mirror of
https://github.com/packwiz/packwiz.git
synced 2025-04-19 21:16:30 +02:00
214 lines
5.4 KiB
Go
214 lines
5.4 KiB
Go
package core
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
// Index is a representation of the index.toml file for referencing all the files in a pack.
|
|
type Index struct {
|
|
HashFormat string `toml:"hash-format"`
|
|
Files []IndexFile `toml:"files"`
|
|
flags Flags
|
|
indexFile string
|
|
}
|
|
|
|
// IndexFile is a file in the index
|
|
type IndexFile struct {
|
|
// Files are stored in relative forward-slash format to the index file
|
|
File string `toml:"file"`
|
|
Hash string `toml:"hash"`
|
|
HashFormat string `toml:"hash-format,omitempty"`
|
|
Alias string `toml:"alias,omitempty"`
|
|
MetaFile bool `toml:"metafile,omitempty"` // True when it is a .toml metadata file
|
|
fileExistsTemp bool
|
|
}
|
|
|
|
// LoadIndex attempts to load the index file from a path
|
|
func LoadIndex(indexFile string, flags Flags) (Index, error) {
|
|
data, err := ioutil.ReadFile(indexFile)
|
|
if err != nil {
|
|
return Index{}, err
|
|
}
|
|
var index Index
|
|
if _, err := toml.Decode(string(data), &index); err != nil {
|
|
return Index{}, err
|
|
}
|
|
index.flags = flags
|
|
index.indexFile = indexFile
|
|
if len(index.HashFormat) == 0 {
|
|
index.HashFormat = "sha256"
|
|
}
|
|
return index, nil
|
|
}
|
|
|
|
// RemoveFile removes a file from the index.
|
|
func (in *Index) RemoveFile(path string) error {
|
|
relPath, err := filepath.Rel(filepath.Dir(in.indexFile), path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
i := 0
|
|
for _, file := range in.Files {
|
|
if filepath.Clean(filepath.FromSlash(file.File)) != relPath {
|
|
// Keep file, as it doesn't match
|
|
in.Files[i] = file
|
|
i++
|
|
}
|
|
}
|
|
in.Files = in.Files[:i]
|
|
return nil
|
|
}
|
|
|
|
// resortIndex sorts Files by file name
|
|
func (in *Index) resortIndex() {
|
|
sort.SliceStable(in.Files, func(i, j int) bool {
|
|
// TODO: Compare by alias if names are equal?
|
|
// TODO: Remove duplicated entries? (compound key on file/alias?)
|
|
return in.Files[i].File < in.Files[j].File
|
|
})
|
|
}
|
|
|
|
// UpdateFile calculates the hash for a given path and updates it in the index
|
|
func (in *Index) UpdateFile(path string) error {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
// Hash usage strategy (may change):
|
|
// Just use SHA256, overwrite existing hash regardless of what it is
|
|
// May update later to continue using the same hash that was already being used
|
|
h := sha256.New()
|
|
if _, err := io.Copy(h, f); err != nil {
|
|
return err
|
|
}
|
|
hashString := hex.EncodeToString(h.Sum(nil))
|
|
|
|
// Find in index
|
|
found := false
|
|
relPath, err := filepath.Rel(filepath.Dir(in.indexFile), path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for k, v := range in.Files {
|
|
if filepath.Clean(filepath.FromSlash(v.File)) == relPath {
|
|
found = true
|
|
// Update hash
|
|
in.Files[k].Hash = hashString
|
|
if in.HashFormat == "sha256" {
|
|
in.Files[k].HashFormat = ""
|
|
} else {
|
|
in.Files[k].HashFormat = "sha256"
|
|
}
|
|
// Mark this file as found
|
|
in.Files[k].fileExistsTemp = true
|
|
// Clean up path if it's untidy
|
|
in.Files[k].File = filepath.ToSlash(relPath)
|
|
// Don't break out of loop, as there may be aliased versions that
|
|
// also need to be updated
|
|
}
|
|
}
|
|
if !found {
|
|
newFile := IndexFile{
|
|
File: filepath.ToSlash(relPath),
|
|
Hash: hashString,
|
|
fileExistsTemp: true,
|
|
}
|
|
// Override hash format for this file, if the whole index isn't sha256
|
|
if in.HashFormat != "sha256" {
|
|
newFile.HashFormat = "sha256"
|
|
}
|
|
// If the file is in the mods folder, set MetaFile to true (mods are metafiles by default)
|
|
// This is incredibly powerful: you can put a normal jar in the mods folder just by
|
|
// setting MetaFile to false. Or you can use the "mod" metadata system for other types
|
|
// of files, like CraftTweaker resources.
|
|
absFileDir, err := filepath.Abs(filepath.Dir(path))
|
|
if err == nil {
|
|
absModsDir, err := filepath.Abs(in.flags.ModsFolder)
|
|
if err == nil {
|
|
if absFileDir == absModsDir {
|
|
newFile.MetaFile = true
|
|
}
|
|
}
|
|
}
|
|
|
|
in.Files = append(in.Files, newFile)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Refresh updates the hashes of all the files in the index, and adds new files to the index
|
|
func (in *Index) Refresh() error {
|
|
// TODO: If needed, multithreaded hashing
|
|
// for i := 0; i < runtime.NumCPU(); i++ {}
|
|
|
|
// Get fileinfos of pack.toml and index to compare them
|
|
// Case-sensitivity?
|
|
pathPF, _ := filepath.Abs(in.flags.PackFile)
|
|
pathIndex, _ := filepath.Abs(in.indexFile)
|
|
|
|
// TODO: A method of specifying pack root directory?
|
|
// TODO: A method of excluding files
|
|
packRoot := filepath.Dir(in.flags.PackFile)
|
|
err := filepath.Walk(packRoot, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
// TODO: Handle errors on individual files properly
|
|
return err
|
|
}
|
|
// Exit if the files are the same as the pack/index files
|
|
absPath, _ := filepath.Abs(path)
|
|
if absPath == pathPF || absPath == pathIndex {
|
|
return nil
|
|
}
|
|
// Exit if this is a directory
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
return in.UpdateFile(path)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check all the files exist, remove them if they don't
|
|
i := 0
|
|
for _, file := range in.Files {
|
|
if file.fileExistsTemp {
|
|
// Keep file if it exists (already checked in UpdateFile)
|
|
in.Files[i] = file
|
|
i++
|
|
}
|
|
}
|
|
in.Files = in.Files[:i]
|
|
|
|
in.resortIndex()
|
|
return nil
|
|
}
|
|
|
|
// Write saves the index file
|
|
func (in Index) Write() error {
|
|
f, err := os.Create(in.indexFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
enc := toml.NewEncoder(f)
|
|
// Disable indentation
|
|
enc.Indent = ""
|
|
return enc.Encode(in)
|
|
}
|
|
|