ficsit-cli-flake/cli/cache/mod_details.go

179 lines
4.2 KiB
Go

package cache
import (
"archive/zip"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"strings"
"github.com/mircearoata/pubgrub-go/pubgrub/semver"
"github.com/puzpuzpuz/xsync/v3"
"github.com/spf13/viper"
)
const IconFilename = "Resources/Icon128.png" // This is the path UE expects for the icon
type Mod struct {
ModReference string
Name string
Author string
Icon *string
LatestVersion string
}
var loadedMods *xsync.MapOf[string, Mod]
func GetCacheMods() (*xsync.MapOf[string, Mod], error) {
if loadedMods != nil {
return loadedMods, nil
}
return LoadCacheMods()
}
func GetCacheMod(mod string) (Mod, error) {
cache, err := GetCacheMods()
if err != nil {
return Mod{}, err
}
value, _ := cache.Load(mod)
return value, nil
}
func LoadCacheMods() (*xsync.MapOf[string, Mod], error) {
loadedMods = xsync.NewMapOf[string, Mod]()
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
if _, err := os.Stat(downloadCache); os.IsNotExist(err) {
return loadedMods, nil
}
items, err := os.ReadDir(downloadCache)
if err != nil {
return nil, fmt.Errorf("failed reading download cache: %w", err)
}
for _, item := range items {
if item.IsDir() {
continue
}
_, err = addFileToCache(item.Name())
if err != nil {
slog.Error("failed to add file to cache", slog.String("file", item.Name()), slog.Any("err", err))
}
}
return loadedMods, nil
}
func addFileToCache(filename string) (*Mod, error) {
cacheFile, err := readCacheFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read cache file: %w", err)
}
loadedMods.Compute(cacheFile.ModReference, func(oldValue Mod, loaded bool) (Mod, bool) {
if !loaded {
return *cacheFile, false
}
oldVersion, err := semver.NewVersion(oldValue.LatestVersion)
if err != nil {
slog.Error("failed to parse version", slog.String("version", oldValue.LatestVersion), slog.Any("err", err))
return *cacheFile, false
}
newVersion, err := semver.NewVersion(cacheFile.LatestVersion)
if err != nil {
slog.Error("failed to parse version", slog.String("version", cacheFile.LatestVersion), slog.Any("err", err))
return oldValue, false
}
if newVersion.Compare(oldVersion) > 0 {
return *cacheFile, false
}
return oldValue, false
})
return cacheFile, nil
}
func readCacheFile(filename string) (*Mod, error) {
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
path := filepath.Join(downloadCache, filename)
stat, err := os.Stat(path)
if err != nil {
return nil, fmt.Errorf("failed to stat file: %w", err)
}
zipFile, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer zipFile.Close()
size := stat.Size()
reader, err := zip.NewReader(zipFile, size)
if err != nil {
return nil, fmt.Errorf("failed to read zip: %w", err)
}
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, fmt.Errorf("failed to open uplugin file: %w", err)
}
var uplugin UPlugin
data, err := io.ReadAll(upluginReader)
if err != nil {
return nil, fmt.Errorf("failed to read uplugin file: %w", err)
}
if err := json.Unmarshal(data, &uplugin); err != nil {
return nil, fmt.Errorf("failed to unmarshal uplugin file: %w", err)
}
modReference := strings.TrimSuffix(upluginFile.Name, ".uplugin")
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, fmt.Errorf("failed to open icon file: %w", err)
}
defer iconReader.Close()
data, err := io.ReadAll(iconReader)
if err != nil {
return nil, fmt.Errorf("failed to read icon file: %w", err)
}
iconData := base64.StdEncoding.EncodeToString(data)
icon = &iconData
}
return &Mod{
ModReference: modReference,
Name: uplugin.FriendlyName,
Author: uplugin.CreatedBy,
Icon: icon,
LatestVersion: uplugin.SemVersion,
}, nil
}