ficsit-cli-flake/cli/cache/integrity.go
2024-02-02 01:34:22 +02:00

118 lines
2.8 KiB
Go

package cache
import (
"encoding/json"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"time"
"github.com/puzpuzpuz/xsync/v3"
"github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/utils"
)
type hashInfo struct {
Modified time.Time
Hash string
Size int64
}
var hashCache *xsync.MapOf[string, hashInfo]
var integrityFilename = ".integrity"
func getFileHash(file string) (string, error) {
if hashCache == nil {
loadHashCache()
}
cachedHash, ok := hashCache.Load(file)
if !ok {
return cacheFileHash(file)
}
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
stat, err := os.Stat(filepath.Join(downloadCache, file))
if err != nil {
return "", fmt.Errorf("failed to stat file: %w", err)
}
if stat.Size() != cachedHash.Size || stat.ModTime() != cachedHash.Modified {
return cacheFileHash(file)
}
return cachedHash.Hash, nil
}
func cacheFileHash(file string) (string, error) {
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
stat, err := os.Stat(filepath.Join(downloadCache, file))
if err != nil {
return "", fmt.Errorf("failed to stat file: %w", err)
}
f, err := os.Open(filepath.Join(downloadCache, file))
if err != nil {
return "", fmt.Errorf("failed to open file: %w", err)
}
defer f.Close()
hash, err := utils.SHA256Data(f)
if err != nil {
return "", fmt.Errorf("failed to hash file: %w", err)
}
hashCache.Store(file, hashInfo{
Hash: hash,
Size: stat.Size(),
Modified: stat.ModTime(),
})
saveHashCache()
return hash, nil
}
func loadHashCache() {
hashCache = xsync.NewMapOf[string, hashInfo]()
cacheFile := filepath.Join(viper.GetString("cache-dir"), "downloadCache", integrityFilename)
if _, err := os.Stat(cacheFile); os.IsNotExist(err) {
return
}
f, err := os.Open(cacheFile)
if err != nil {
slog.Warn("failed to open hash cache, recreating", slog.Any("err", err))
return
}
defer f.Close()
hashCacheJSON, err := io.ReadAll(f)
if err != nil {
slog.Warn("failed to read hash cache, recreating", slog.Any("err", err))
return
}
var plainCache map[string]hashInfo
if err := json.Unmarshal(hashCacheJSON, &plainCache); err != nil {
slog.Warn("failed to unmarshal hash cache, recreating", slog.Any("err", err))
return
}
for k, v := range plainCache {
hashCache.Store(k, v)
}
}
func saveHashCache() {
cacheFile := filepath.Join(viper.GetString("cache-dir"), "downloadCache", integrityFilename)
plainCache := make(map[string]hashInfo, hashCache.Size())
hashCache.Range(func(k string, v hashInfo) bool {
plainCache[k] = v
return true
})
hashCacheJSON, err := json.Marshal(plainCache)
if err != nil {
slog.Warn("failed to marshal hash cache", slog.Any("err", err))
return
}
if err := os.WriteFile(cacheFile, hashCacheJSON, 0o755); err != nil {
slog.Warn("failed to write hash cache", slog.Any("err", err))
return
}
}