esp-modman/manager.go

396 lines
7.8 KiB
Go

package main
import (
"bufio"
"encoding/csv"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
)
type App struct {
ConfigDirPath string
}
func (game *Game) CachePath() (string, error) {
var path, pathError = os.UserCacheDir()
if pathError != nil {
return "", pathError
}
path = filepath.Join(path, "modman", game.ID)
if mkdirError := os.MkdirAll(path, 0755); mkdirError != nil {
return "", mkdirError
}
return path, nil
}
func (game *Game) Clean() error {
var cachePath, cachePathError = game.CachePath()
if cachePathError != nil {
return cachePathError
}
var deployedListPath = filepath.Join(cachePath, "backup", "deployed.txt")
if backupFile, openError := os.Open(deployedListPath); !(os.IsNotExist(openError)) {
defer backupFile.Close()
var scanner = bufio.NewScanner(backupFile)
for scanner.Scan() {
var
}
}
return nil
}
func (game *Game) ConfigPath() (string, error) {
var path, pathError = os.UserConfigDir()
if pathError != nil {
return "", pathError
}
path = filepath.Join(path, "modman", game.ID)
if mkdirError := os.MkdirAll(path, 0755); mkdirError != nil {
return "", mkdirError
}
return path, nil
}
func (game *Game) Deploy() error {
var cachePath, cachePathError = game.CachePath()
if cachePathError != nil {
return cachePathError
}
var configPath, configPathError = game.ConfigPath()
if configPathError != nil {
return configPathError
}
var backupPath = filepath.Join(cachePath, "backup")
var deployedListPath = filepath.Join(backupPath, "deployed.txt")
if backupFile, openError := os.Open(deployedListPath); !(os.IsNotExist(openError)) {
defer backupFile.Close()
var scanner = bufio.NewScanner(backupFile)
for scanner.Scan() {
var
}
}
var modsPath = filepath.Join(configPath, "mods")
for i := range game.ModOrder {
var mod = game.ModOrder[i]
if mod.IsEnabled {
var modPath = filepath.Join(modsPath, mod.Name)
if walkError := filepath.WalkDir(modPath, func(
path string, dirEntry fs.DirEntry, dirError error) error {
if dirError != nil {
return dirError
}
var fileMode = dirEntry.Type()
if !(fileMode.IsDir()) {
var localPath, relativeError = filepath.Rel(modPath, path)
if relativeError != nil {
return relativeError
}
var linkPath = filepath.Join(game.Path, localPath)
if pathError := os.MkdirAll(filepath.Dir(linkPath),
fileMode.Perm()); pathError != nil {
return pathError
}
if _, statError := os.Stat(linkPath); !(os.IsNotExist(statError)) {
var sourceFile, sourceOpenError = os.Open(linkPath)
if sourceOpenError != nil {
return sourceOpenError
}
defer sourceFile.Close()
var targetFile, targetOpenError = os.Create(backupPath)
if targetOpenError != nil {
return targetOpenError
}
defer targetFile.Close()
var _, copyError = io.Copy(targetFile, sourceFile)
if copyError != nil {
return copyError
}
}
if linkError := os.Link(path, linkPath); linkError != nil {
return linkError
}
}
return nil
}); walkError != nil {
return walkError
}
}
}
return nil
}
type Extractor func(string, string) error
type Game struct {
ID string
ModOrder []Mod
ModNames map[string]int
Path string
HasUpdated bool
}
func (game *Game) InstallMod(extractor Extractor, archivePath string) error {
var baseName = filepath.Base(archivePath)
var modName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
if _, exists := game.ModNames[modName]; exists {
return fmt.Errorf("mod with name already exists: `%s`", modName)
}
var configPath, pathError = game.ConfigPath()
if pathError != nil {
return pathError
}
if extractError := extractor(archivePath, filepath.Join(
configPath, "mods", modName)); extractError != nil {
return extractError
}
game.ModNames[modName] = len(game.ModOrder)
game.ModOrder = append(game.ModOrder, Mod{
IsEnabled: false,
Name: modName,
})
game.HasUpdated = true
return nil
}
type Mod struct {
IsEnabled bool
Name string
Source string
}
func (game *Game) SwitchMods(isEnabled bool, names []string) error {
if len(names) != 0 {
for i := range names {
var name = names[i]
var index, exists = game.ModNames[name]
if !(exists) {
return fmt.Errorf("mod does not exist: `%s`", name)
}
var mod = game.ModOrder[index]
mod.IsEnabled = isEnabled
game.ModOrder[index] = mod
}
game.HasUpdated = true
}
return nil
}
func WithGame(gameName string, action func(*Game) error) error {
var supportedGames = []string{"fallout4", "falloutnv", "skyrim"}
for i := range supportedGames {
var supportedGame = supportedGames[i]
if gameName == supportedGame {
var game = Game{
ID: supportedGame,
ModOrder: make([]Mod, 0, 512),
ModNames: make(map[string]int),
HasUpdated: false,
Path: "/home/kayomn/.steam/steam/steamapps/common/Fallout 4/Data",
}
var configPath, pathError = game.ConfigPath()
if pathError != nil {
return pathError
}
var manifestPath = filepath.Join(configPath, "mods.csv")
// Load manifest from disk.
{
var manifestFile, openError = os.Open(manifestPath)
if openError == nil {
defer manifestFile.Close()
var manifestReader = csv.NewReader(manifestFile)
var recordValues, recordError = manifestReader.Read()
for recordValues != nil {
if recordError != nil {
return recordError
}
if len(recordValues) < 2 {
return fmt.Errorf("could not read mod manifest data - may be corrupt")
}
var status = recordValues[0]
var name = recordValues[1]
game.ModNames[name] = len(game.ModOrder)
game.ModOrder = append(game.ModOrder, Mod{
IsEnabled: status == "*",
Name: name,
Source: "",
})
recordValues, recordError = manifestReader.Read()
}
}
}
if actionError := action(&game); actionError != nil {
return actionError
}
// Save manifest back to disk.
if game.HasUpdated {
var manifestFile, openError = os.Create(manifestPath)
if openError != nil {
return openError
}
defer manifestFile.Close()
var manifestWriter = csv.NewWriter(manifestFile)
for i := range game.ModOrder {
var mod = game.ModOrder[i]
var status = ""
if mod.IsEnabled {
status = "*"
}
manifestWriter.Write([]string{status, mod.Name, mod.Source})
}
manifestWriter.Flush()
}
return nil
}
}
return fmt.Errorf("%s: game not supported", gameName)
}
func (game *Game) RemoveMods(names []string) error {
var configPath, pathError = game.ConfigPath()
if pathError != nil {
return pathError
}
if len(names) != 0 {
game.HasUpdated = true
for i := range names {
var name = names[i]
var index, exists = game.ModNames[name]
if !(exists) {
return fmt.Errorf("unknown mod: `%s`", name)
}
if removeError := os.RemoveAll(filepath.Join(configPath, name)); removeError != nil {
return removeError
}
game.ModOrder = append(game.ModOrder[:index], game.ModOrder[index+1:]...)
delete(game.ModNames, name)
}
}
return nil
}
func (game *Game) RenameMod(modName string, newName string) error {
var configPath, pathError = game.ConfigPath()
if pathError != nil {
return pathError
}
if _, exists := game.ModNames[modName]; !(exists) {
return fmt.Errorf("no mod with that name exists")
}
if _, nameTaken := game.ModNames[newName]; nameTaken {
return fmt.Errorf("a mod with the new name already exists")
}
var modsPath = filepath.Join(configPath, "mods")
if renameError := os.Rename(filepath.Join(modsPath, modName),
filepath.Join(modsPath, newName)); renameError != nil {
return renameError
}
game.ModNames[newName] = game.ModNames[modName]
delete(game.ModNames, modName)
game.HasUpdated = true
return nil
}