ficsit-cli-flake/cli/cache/cache.go
Vilsol 5f2e60a9e2
feat: multi targets (#44)
* feat: use mod version targets

* chore: lint

* chore: remove unused

* chore: target dev on ci

* fix: rename WindowsNoEditor target to Windows
fix: close file reader

* fix: ensure closure of downloaded mod

* fix: ensure all important events are sent

* fix: lock adding files to cache

---------

Co-authored-by: mircearoata <mircearoatapalade@gmail.com>
2023-12-07 18:57:31 +02:00

169 lines
3.8 KiB
Go

package cache
import (
"archive/zip"
"encoding/base64"
"encoding/json"
"io"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/puzpuzpuz/xsync/v3"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
)
const IconFilename = "Resources/Icon128.png" // This is the path UE expects for the icon
type File struct {
Icon *string
ModReference string
Hash string
Plugin UPlugin
Size int64
}
var loadedCache *xsync.MapOf[string, []File]
func GetCache() (*xsync.MapOf[string, []File], error) {
if loadedCache != nil {
return loadedCache, nil
}
return LoadCache()
}
func GetCacheMod(mod string) ([]File, error) {
cache, err := GetCache()
if err != nil {
return nil, err
}
value, _ := cache.Load(mod)
return value, nil
}
func LoadCache() (*xsync.MapOf[string, []File], error) {
loadedCache = xsync.NewMapOf[string, []File]()
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
if _, err := os.Stat(downloadCache); os.IsNotExist(err) {
return loadedCache, nil
}
items, err := os.ReadDir(downloadCache)
if err != nil {
return nil, errors.Wrap(err, "failed reading download cache")
}
for _, item := range items {
if item.IsDir() {
continue
}
if item.Name() == integrityFilename {
continue
}
_, err = addFileToCache(item.Name())
if err != nil {
log.Err(err).Str("file", item.Name()).Msg("failed to add file to cache")
}
}
return loadedCache, nil
}
func addFileToCache(filename string) (*File, error) {
cacheFile, err := readCacheFile(filename)
if err != nil {
return nil, errors.Wrap(err, "failed to read cache file")
}
loadedCache.Compute(cacheFile.ModReference, func(oldValue []File, _ bool) ([]File, bool) {
return append(oldValue, *cacheFile), false
})
return cacheFile, nil
}
func readCacheFile(filename string) (*File, error) {
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
path := filepath.Join(downloadCache, filename)
stat, err := os.Stat(path)
if err != nil {
return nil, errors.Wrap(err, "failed to stat file")
}
zipFile, err := os.Open(path)
if err != nil {
return nil, errors.Wrap(err, "failed to open file")
}
defer zipFile.Close()
size := stat.Size()
reader, err := zip.NewReader(zipFile, size)
if err != nil {
return nil, errors.Wrap(err, "failed to read zip")
}
var upluginFile *zip.File
for _, file := range reader.File {
if strings.HasSuffix(file.Name, ".uplugin") {
upluginFile = file
break
}
}
if upluginFile == nil {
return nil, errors.New("no uplugin file found in zip")
}
upluginReader, err := upluginFile.Open()
if err != nil {
return nil, errors.Wrap(err, "failed to open uplugin file")
}
var uplugin UPlugin
data, err := io.ReadAll(upluginReader)
if err != nil {
return nil, errors.Wrap(err, "failed to read uplugin file")
}
if err := json.Unmarshal(data, &uplugin); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal uplugin file")
}
modReference := strings.TrimSuffix(upluginFile.Name, ".uplugin")
hash, err := getFileHash(filename)
if err != nil {
return nil, errors.Wrap(err, "failed to get file hash")
}
var iconFile *zip.File
for _, file := range reader.File {
if file.Name == IconFilename {
iconFile = file
break
}
}
var icon *string
if iconFile != nil {
iconReader, err := iconFile.Open()
if err != nil {
return nil, errors.Wrap(err, "failed to open icon file")
}
defer iconReader.Close()
data, err := io.ReadAll(iconReader)
if err != nil {
return nil, errors.Wrap(err, "failed to read icon file")
}
iconData := base64.StdEncoding.EncodeToString(data)
icon = &iconData
}
return &File{
ModReference: modReference,
Hash: hash,
Size: size,
Icon: icon,
Plugin: uplugin,
}, nil
}