Complete code audit

This commit is contained in:
kayomn 2022-12-23 14:58:59 +00:00
parent cde4b75c5d
commit d35eb6016d
4 changed files with 385 additions and 446 deletions

2
go.mod
View File

@ -4,5 +4,5 @@ go 1.19
require ( require (
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
sauce.pizzawednes.day/kayomn/ini-gusher v0.0.0-20221220232638-8a897dbd24aa // indirect sauce.pizzawednes.day/kayomn/ini-gusher v0.0.0-20221223145344-d68ddf116418 // indirect
) )

4
go.sum
View File

@ -1,5 +1,5 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sauce.pizzawednes.day/kayomn/ini-gusher v0.0.0-20221220232638-8a897dbd24aa h1:Co1oVW0IMEIwk3tlQ2dJEFzNSd4r6tkPb2/6mxjnsOo= sauce.pizzawednes.day/kayomn/ini-gusher v0.0.0-20221223145344-d68ddf116418 h1:lAFE8yL+87+JGZROd2Wc3PG2BUWyiKe0xOWTNyVEiAU=
sauce.pizzawednes.day/kayomn/ini-gusher v0.0.0-20221220232638-8a897dbd24aa/go.mod h1:lniG+VCTpfcWAKKudVYLrS5NIpRx90H3mQklQNn+eK0= sauce.pizzawednes.day/kayomn/ini-gusher v0.0.0-20221223145344-d68ddf116418/go.mod h1:lniG+VCTpfcWAKKudVYLrS5NIpRx90H3mQklQNn+eK0=

145
main.go
View File

@ -32,32 +32,16 @@ var commands = []Command{
requiredArguments[0], requiredArguments[1]) requiredArguments[0], requiredArguments[1])
} }
var game, gameOpenError = OpenGame(providedArguments[0])
if gameOpenError != nil {
return "", gameOpenError
}
defer func() {
if closeError := game.Close(); closeError != nil {
panic(closeError)
}
}()
var archivePaths = providedArguments[1:] var archivePaths = providedArguments[1:]
if len(archivePaths) > 1 { if game, openGameError := LoadGame(providedArguments[0]); openGameError == nil {
for _, archivePath := range archivePaths { if installError := game.InstallMods(archivePaths); installError != nil {
if installError := game.InstallMod(archivePath); installError != nil { return "", installError
return "", installError
}
} }
return "mods installed", nil
} }
if installError := game.InstallMod(archivePaths[0]); installError != nil { if len(archivePaths) > 1 {
return "", installError return "mods installed", nil
} }
return "mod installed", nil return "mod installed", nil
@ -99,23 +83,23 @@ var commands = []Command{
requiredArguments[0], requiredArguments[1]) requiredArguments[0], requiredArguments[1])
} }
var game, gameOpenError = OpenGame(providedArguments[0]) var modNames = providedArguments[1:]
if gameOpenError != nil { if game, openGameError := LoadGame(providedArguments[0]); openGameError == nil {
return "", gameOpenError for _, name := range modNames {
} if removeError := game.RemoveMod(name); removeError != nil {
return "", removeError
defer func() { }
if closeError := game.Close(); closeError != nil {
panic(closeError)
} }
}() } else {
return "", openGameError
if removeModsError := game.RemoveMods(providedArguments[1:]); removeModsError != nil {
return "", removeModsError
} }
return "removed mods", nil if len(modNames) > 1 {
return "removed mods", nil
}
return "removed mod", nil
}, },
}, },
@ -131,22 +115,12 @@ var commands = []Command{
requiredArguments[0], requiredArguments[1], requiredArguments[2]) requiredArguments[0], requiredArguments[1], requiredArguments[2])
} }
var game, gameOpenError = OpenGame(providedArguments[0]) if game, openGameError := LoadGame(providedArguments[0]); openGameError == nil {
if renameError := game.RenameMod(providedArguments[1], providedArguments[2]); renameError != nil {
if gameOpenError != nil { return "", renameError
return "", gameOpenError
}
defer func() {
if closeError := game.Close(); closeError != nil {
panic(closeError)
} }
}() } else {
return "", openGameError
if renameError := game.RenameMod(
providedArguments[1], providedArguments[2]); renameError != nil {
return "", renameError
} }
return "renamed", nil return "renamed", nil
@ -165,29 +139,20 @@ var commands = []Command{
requiredArguments[0], requiredArguments[1]) requiredArguments[0], requiredArguments[1])
} }
var game, gameOpenError = OpenGame(providedArguments[0])
if gameOpenError != nil {
return "", gameOpenError
}
defer func() {
if closeError := game.Close(); closeError != nil {
panic(closeError)
}
}()
var format = providedArguments[1]
var formatter, formatterExists = formatters[format]
if !(formatterExists) {
return "", fmt.Errorf("unsupported format: `%s`", format)
}
var manifestBuilder = strings.Builder{} var manifestBuilder = strings.Builder{}
if formatError := formatter(&manifestBuilder, game.Mods); formatError != nil { if game, openGameError := LoadGame(providedArguments[0]); openGameError == nil {
return "", formatError var format = providedArguments[1]
if formatter, exists := formatters[format]; exists {
if formatError := formatter(&manifestBuilder, game.Mods); formatError != nil {
return "", formatError
}
} else {
return "", fmt.Errorf("unsupported format: `%s`", format)
}
} else {
return "", openGameError
} }
return manifestBuilder.String(), nil return manifestBuilder.String(), nil
@ -206,26 +171,12 @@ var commands = []Command{
requiredArguments[0], requiredArguments[1]) requiredArguments[0], requiredArguments[1])
} }
var game, gameOpenError = OpenGame(providedArguments[0]) if game, openGameError := LoadGame(providedArguments[0]); openGameError != nil {
if deployError := game.Deploy(providedArguments[1:]); deployError != nil {
if gameOpenError != nil {
return "", gameOpenError
}
defer func() {
if closeError := game.Close(); closeError != nil {
panic(closeError)
}
}()
if cleanError := game.CleanDeployedMods(); cleanError != nil {
return "", cleanError
}
for _, modName := range providedArguments[1:] {
if deployError := game.DeployMod(modName); deployError != nil {
return "", deployError return "", deployError
} }
} else {
return "", openGameError
} }
return "deployed", nil return "deployed", nil
@ -243,20 +194,12 @@ var commands = []Command{
return "", fmt.Errorf("expected %s", requiredArguments[0]) return "", fmt.Errorf("expected %s", requiredArguments[0])
} }
var game, gameOpenError = OpenGame(arguments[0]) if game, openGameError := LoadGame(arguments[0]); openGameError == nil {
if cleanError := game.Clean(); cleanError != nil {
if gameOpenError != nil { return "", cleanError
return "", gameOpenError
}
defer func() {
if closeError := game.Close(); closeError != nil {
panic(closeError)
} }
}() } else {
return "", openGameError
if cleanDeployedModsError := game.CleanDeployedMods(); cleanDeployedModsError != nil {
return "", cleanDeployedModsError
} }
return "cleaned", nil return "cleaned", nil

View File

@ -13,29 +13,23 @@ import (
"sauce.pizzawednes.day/kayomn/ini-gusher" "sauce.pizzawednes.day/kayomn/ini-gusher"
) )
func (game *Game) CleanDeployedMods() error { func (game *Game) Clean() error {
// Clean up currently deployed files first. // Clean up currently deployed files first.
for _, filePath := range game.DeployedFilePaths { for _, filePath := range game.DeployedFilePaths {
var gameFilePath = filepath.Join(game.Path, filePath) if removeError := os.Remove(filepath.Join(
game.Path, filePath)); (removeError != nil) && (!(os.IsNotExist(removeError))) {
if removeError := os.Remove(gameFilePath); removeError != nil { return removeError
if !(os.IsNotExist(removeError)) {
return removeError
}
} }
} }
game.DeployedFilePaths = game.DeployedFilePaths[:0] game.DeployedFilePaths = game.DeployedFilePaths[:0]
var overwriteDirPath = filepath.Join(cachePath(), game.Name, "overwritten")
// Then restore all files overwritten by previously deployment. // Then restore all files overwritten by previously deployment.
for _, filePath := range game.OverwrittenFilePaths { for _, filePath := range game.OverwrittenFilePaths {
var backupDirPath, backupDirPathError = game.cachePath("overwritten") if renameError := os.Rename(filepath.Join(overwriteDirPath, filePath),
if backupDirPathError != nil {
return backupDirPathError
}
if renameError := os.Rename(filepath.Join(backupDirPath, filePath),
filepath.Join(game.Path, filePath)); renameError != nil { filepath.Join(game.Path, filePath)); renameError != nil {
return renameError return renameError
@ -44,199 +38,119 @@ func (game *Game) CleanDeployedMods() error {
game.OverwrittenFilePaths = game.OverwrittenFilePaths[:0] game.OverwrittenFilePaths = game.OverwrittenFilePaths[:0]
return nil return game.saveDeployment()
} }
func (game *Game) Close() error { func (game *Game) Deploy(names []string) error {
// Write deployed files to disk. for _, name := range names {
var deployedListPath, deployedListPathError = game.cachePath("deployed.txt") var mod, exists = game.Mods[name]
if deployedListPathError != nil { if !(exists) {
return deployedListPathError return fmt.Errorf("mod does not exist: %s", name)
}
var deployedListFile, deployedListCreateError = os.Create(deployedListPath)
if deployedListCreateError != nil {
return deployedListCreateError
}
defer func() {
if syncError := deployedListFile.Sync(); syncError != nil {
panic(syncError)
} }
if closeError := deployedListFile.Close(); closeError != nil { var installPath = fmt.Sprintf("%s.%s",
panic(closeError) filepath.Join(configPath(), game.Name, name), mod.Format)
}
}()
var deployedListWriter = bufio.NewWriter(deployedListFile) switch mod.Format {
case "zip":
var zipReadCloser, openError = zip.OpenReader(installPath)
defer func() { if openError != nil {
if flushError := deployedListWriter.Flush(); flushError != nil { return openError
panic(flushError)
}
}()
for _, filePath := range game.DeployedFilePaths {
if _, writeError := deployedListWriter.WriteString(filePath); writeError != nil {
return writeError
}
if writeError := deployedListWriter.WriteByte('\n'); writeError != nil {
return writeError
}
}
// Read mod info from disk.
var modInfoPath, modInfoPathError = game.configPath("mods.ini")
if modInfoPathError != nil {
return modInfoPathError
}
var modInfoFile, modInfoCreateError = os.Create(modInfoPath)
if modInfoCreateError != nil {
return modInfoCreateError
}
defer func() {
if closeError := modInfoFile.Close(); closeError != nil {
panic(closeError)
}
}()
var modInfoEntries = make([]ini.Entry, 0, len(game.Mods)*3)
for name, mod := range game.Mods {
modInfoEntries = append(modInfoEntries, ini.Entry{
Section: name,
Key: "source",
Value: mod.Source,
})
modInfoEntries = append(modInfoEntries, ini.Entry{
Section: name,
Key: "format",
Value: mod.Format,
})
modInfoEntries = append(modInfoEntries, ini.Entry{
Section: name,
Key: "version",
Value: mod.Version,
})
}
return ini.Write(modInfoFile, modInfoEntries)
}
func (game *Game) DeployMod(name string) error {
var mod, exists = game.Mods[name]
if !(exists) {
return fmt.Errorf("mod does not exist: %s", name)
}
var archivePath, archivePathError = game.configPath(name)
if archivePathError != nil {
return archivePathError
}
switch mod.Format {
case "zip":
archivePath += ".zip"
var zipReadCloser, zipReadCloserOpenError = zip.OpenReader(archivePath)
if zipReadCloserOpenError != nil {
return zipReadCloserOpenError
}
defer func() {
if closeError := zipReadCloser.Close(); closeError != nil {
panic(closeError)
}
}()
for _, zipFile := range zipReadCloser.File {
var deployPath = filepath.Join(game.Path, zipFile.Name)
var fileMode = zipFile.Mode()
if dirError := os.MkdirAll(filepath.Dir(deployPath), fileMode); dirError != nil {
return dirError
}
if zipFile.FileInfo().IsDir() {
// All work is done for creating a directory, rest is just for files.
continue
}
var file, openFileError = os.OpenFile(
deployPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, fileMode)
if openFileError != nil {
if !(os.IsExist(openFileError)) {
return openFileError
}
var backupPath, backupPathError = game.cachePath("overwritten") // deployPath
if backupPathError != nil {
return backupPathError
}
backupPath = filepath.Join(backupPath, zipFile.Name)
if dirError := os.MkdirAll(filepath.Dir(backupPath), 0755); dirError != nil {
return dirError
}
if renameError := os.Rename(deployPath, backupPath); renameError != nil {
return renameError
}
game.OverwrittenFilePaths = append(game.OverwrittenFilePaths, zipFile.Name)
file, openFileError = os.OpenFile(
deployPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, fileMode)
if openFileError != nil {
return openFileError
}
}
var zipReadCloser, zipFileOpenError = zipFile.Open()
if zipFileOpenError != nil {
return zipFileOpenError
} }
defer func() { defer func() {
if syncError := file.Sync(); syncError != nil { if closeError := zipReadCloser.Close(); closeError != nil {
panic(syncError) // Zip read closer will not have any pending I/O operations nor is it possible
} // to have already been closed.
if closeError := file.Close(); closeError != nil {
panic(closeError) panic(closeError)
} }
}() }()
if _, copyError := io.Copy(file, zipReadCloser); copyError != nil { for _, zipFile := range zipReadCloser.File {
return copyError var deployPath = filepath.Join(game.Path, zipFile.Name)
var fileMode = zipFile.Mode()
if dirError := os.MkdirAll(filepath.Dir(deployPath), fileMode); dirError != nil {
return dirError
}
if zipFile.FileInfo().IsDir() {
// All work is done for creating a directory, rest is just for files.
continue
}
// Backup up any pre-existing file before it is overwritten by Zip entry
// extraction.
if fileInfo, statError := os.Stat(deployPath); statError == nil {
var backupPath = filepath.Join(
cachePath(), game.Name, "overwritten", zipFile.Name)
if dirError := os.MkdirAll(filepath.Dir(backupPath), fileInfo.Mode()); dirError != nil {
if closeError := zipReadCloser.Close(); closeError != nil {
return closeError
}
return dirError
}
if renameError := os.Rename(deployPath, backupPath); renameError != nil {
if closeError := zipReadCloser.Close(); closeError != nil {
return closeError
}
return renameError
}
game.OverwrittenFilePaths = append(game.OverwrittenFilePaths, zipFile.Name)
} else if !(os.IsNotExist(statError)) {
if closeError := zipReadCloser.Close(); closeError != nil {
return closeError
}
return statError
}
if file, createError := os.Create(deployPath); createError == nil {
var zipFile, ioError = zipFile.Open()
if ioError == nil {
_, ioError = io.Copy(file, zipFile)
}
if syncError := file.Sync(); (syncError != nil) && (ioError == nil) {
ioError = syncError
}
if closeError := file.Close(); (closeError != nil) && (ioError == nil) {
ioError = closeError
}
if closeError := zipFile.Close(); closeError != nil {
ioError = closeError
}
if ioError != nil {
return ioError
}
} else {
if closeError := zipReadCloser.Close(); closeError != nil {
return closeError
}
return createError
}
game.DeployedFilePaths = append(game.DeployedFilePaths, zipFile.Name)
} }
game.DeployedFilePaths = append(game.DeployedFilePaths, zipFile.Name) default:
return fmt.Errorf("unsupported mod format: %s", mod.Format)
} }
default:
return fmt.Errorf("unsupported mod format: %s", mod.Format)
} }
return nil return game.saveDeployment()
} }
type Game struct { type Game struct {
@ -247,91 +161,93 @@ type Game struct {
Path string Path string
} }
func (game *Game) InstallMod(archivePath string) error { func (game *Game) InstallMods(archivePaths []string) error {
var archiveBaseName = filepath.Base(archivePath) var installDirPath = filepath.Join(configPath(), game.Name)
var archiveExtension = filepath.Ext(archiveBaseName)
if len(archiveExtension) == 0 { for _, archivePath := range archivePaths {
return fmt.Errorf("unknown archive format: %s", archiveExtension) var archiveName = filepath.Base(archivePath)
} var archiveExtension = filepath.Ext(archiveName)
var name = strings.TrimSuffix(archiveBaseName, archiveExtension) if len(archiveExtension) == 0 {
return fmt.Errorf("unknown archive format: %s", archiveExtension)
if _, exists := game.Mods[name]; exists {
return fmt.Errorf("mod with name already exists: `%s`", name)
}
// Copy archive into installation directory.
{
var installPath, installPathError = game.configPath(archiveBaseName)
if installPathError != nil {
return installPathError
} }
var sourceFile, sourceOpenError = os.Open(archivePath) var name = strings.TrimSuffix(archiveName, archiveExtension)
if sourceOpenError != nil { if _, exists := game.Mods[name]; exists {
return sourceOpenError return fmt.Errorf("mod with name already exists: `%s`", name)
} }
defer sourceFile.Close() // Copy archive into installation directory.
if archiveFile, openError := os.Open(archivePath); openError == nil {
defer func() {
if closeError := archiveFile.Close(); closeError != nil {
// Archive file will not have any pending I/O operations nor is it possible to have
// already been closed.
panic(closeError)
}
}()
var targetFile, targetCreateError = os.Create(installPath) var archiveFileInfo, statError = archiveFile.Stat()
if targetCreateError != nil { if statError != nil {
return targetCreateError return statError
}
if dirError := os.MkdirAll(installDirPath, archiveFileInfo.Mode()); dirError != nil {
return dirError
}
if installFile, createError := os.Create(filepath.Join(installDirPath, archiveName)); createError == nil {
var _, copyError = io.Copy(installFile, archiveFile)
var syncError = installFile.Sync()
var closeError = installFile.Close()
if (copyError != nil) || (syncError != nil) {
return fmt.Errorf("failed to install mod")
}
if closeError != nil {
return closeError
}
} else {
return createError
}
} else {
return openError
} }
defer targetFile.Close() game.Mods[name] = Mod{
Format: archiveExtension[1:],
if _, copyError := io.Copy(targetFile, sourceFile); copyError != nil { Source: archivePath,
return copyError Version: "",
} }
} }
game.Mods[name] = Mod{ return game.saveMods()
Format: archiveExtension[1:],
Source: archivePath,
Version: "",
}
return nil
} }
func (game *Game) Load() error { func (game *Game) Load() error {
// Read deployed files from disk. // Read deployed files from disk.
var deployedListPath, deployedListPathError = game.cachePath("deployed.txt") var deployedListPath = filepath.Join(cachePath(), game.Name, "deployed.txt")
if deployedListPathError != nil {
return deployedListPathError
}
if file, openError := os.Open(deployedListPath); openError == nil { if file, openError := os.Open(deployedListPath); openError == nil {
defer func() { for scanner := bufio.NewScanner(file); scanner.Scan(); {
if closeError := file.Close(); closeError != nil {
panic(closeError)
}
}()
var scanner = bufio.NewScanner(file)
for scanner.Scan() {
game.DeployedFilePaths = append(game.DeployedFilePaths, scanner.Text()) game.DeployedFilePaths = append(game.DeployedFilePaths, scanner.Text())
} }
if closeError := file.Close(); closeError != nil {
return closeError
}
} else if !(os.IsNotExist(openError)) { } else if !(os.IsNotExist(openError)) {
return openError return openError
} }
// Read overwritten files from disk. // Read overwritten game files from disk.
var overwriteDirPath, overwriteDirPathError = game.cachePath("overwritten") var overwrittenFilesDirPath = filepath.Join(cachePath(), game.Name, "overwritten")
if overwriteDirPathError != nil { if _, statError := os.Stat(overwrittenFilesDirPath); statError == nil {
return overwriteDirPathError if walkError := filepath.WalkDir(overwrittenFilesDirPath, func(
}
if _, statError := os.Stat(overwriteDirPath); statError == nil {
if walkError := filepath.WalkDir(overwriteDirPath, func(
path string, dirEntry fs.DirEntry, walkError error) error { path string, dirEntry fs.DirEntry, walkError error) error {
if walkError != nil { if walkError != nil {
@ -351,15 +267,11 @@ func (game *Game) Load() error {
} }
// Read mod info from disk. // Read mod info from disk.
var modInfoPath, modInfoPathError = game.configPath("mods.ini") if file, openError := os.Open(filepath.Join(configPath(), game.Name, "mods.ini")); openError == nil {
if modInfoPathError != nil {
return modInfoPathError
}
if file, openError := os.Open(modInfoPath); openError == nil {
defer func() { defer func() {
if closeError := file.Close(); closeError != nil { if closeError := file.Close(); closeError != nil {
// File will not have any pending I/O operations nor is it possible to have already
// been closed.
panic(closeError) panic(closeError)
} }
}() }()
@ -399,7 +311,7 @@ type Mod struct {
Version string Version string
} }
func OpenGame(name string) (Game, error) { func LoadGame(name string) (Game, error) {
var configPath, configPathError = os.UserConfigDir() var configPath, configPathError = os.UserConfigDir()
if configPathError != nil { if configPathError != nil {
@ -425,7 +337,7 @@ func OpenGame(name string) (Game, error) {
OverwrittenFilePaths: make([]string, 0, 512), OverwrittenFilePaths: make([]string, 0, 512),
DeployedFilePaths: make([]string, 0, 512), DeployedFilePaths: make([]string, 0, 512),
Mods: make(map[string]Mod), Mods: make(map[string]Mod),
Path: "/home/kayomn/.steam/steam/steamapps/common/Fallout 4/Data", Path: entry.Key,
} }
if loadError := game.Load(); loadError != nil { if loadError := game.Load(); loadError != nil {
@ -439,84 +351,93 @@ func OpenGame(name string) (Game, error) {
return Game{}, fmt.Errorf("game not registered: %s", name) return Game{}, fmt.Errorf("game not registered: %s", name)
} }
func (game *Game) RemoveMods(names []string) error { func (game *Game) RemoveMod(name string) error {
for _, name := range names { if _, exists := game.Mods[name]; !(exists) {
if _, exists := game.Mods[name]; !(exists) { return fmt.Errorf("unknown mod: `%s`", name)
return fmt.Errorf("unknown mod: `%s`", name)
}
var path, pathError = game.configPath(name)
if pathError != nil {
return pathError
}
if removeError := os.RemoveAll(path); removeError != nil {
return removeError
}
delete(game.Mods, name)
} }
return nil if removeError := os.RemoveAll(filepath.Join(
configPath(), game.Name, name)); removeError != nil {
return removeError
}
delete(game.Mods, name)
return game.saveMods()
} }
func RegisterGame(name string, dataPath string) error { func RegisterGame(name string, dataPath string) error {
var configPath, configPathError = os.UserConfigDir() var gamesPath = filepath.Join(configPath(), "games.ini")
var gameNamePaths = make(map[string]string)
if configPathError != nil { if file, openError := os.Open(gamesPath); openError == nil {
return configPathError defer func() {
if closeError := file.Close(); closeError != nil {
// No way for this to fail.
panic(closeError)
}
}()
var parser = ini.NewParser(file)
for entry := parser.Parse(); !(parser.IsEnd()); entry = parser.Parse() {
if entry.Key == "path" {
gameNamePaths[entry.Section] = entry.Value
}
}
if parserError := parser.Err(); parserError != nil {
return parserError
}
} else if !(os.IsNotExist(openError)) {
return openError
} }
var gamesPath = filepath.Join(configPath, "modman", "games.ini") if file, updateError := os.Create(gamesPath); updateError == nil {
var writer = bufio.NewWriter(file)
var builder = ini.NewBuilder(writer)
var gamesEntries = []ini.Entry{{ for name, path := range gameNamePaths {
Section: name, if buildError := builder.Section(name); buildError != nil {
Key: "path", updateError = buildError
Value: dataPath,
}}
if gamesFile, gamesFileError := os.Open(gamesPath); gamesFileError == nil { break
var gamesParser = ini.NewParser(gamesFile) }
for entry := gamesParser.Parse(); !(gamesParser.IsEnd()); entry = gamesParser.Parse() { if buildError := builder.KeyValue("path", path); buildError != nil {
gamesEntries = append(gamesEntries, entry) updateError = buildError
break
}
} }
if err := gamesFile.Close(); err != nil { if writeError := writer.Flush(); (writeError != nil) && (updateError == nil) {
return err updateError = writeError
} }
if err := gamesParser.Err(); err != nil { if syncError := file.Sync(); (syncError != nil) && (updateError == nil) {
return err updateError = syncError
}
} else if !(os.IsNotExist(gamesFileError)) {
return gamesFileError
}
if gamesFile, gamesFileError := os.Create(gamesPath); gamesFileError == nil {
var writeError = ini.Write(gamesFile, gamesEntries)
if err := gamesFile.Sync(); err != nil {
return err
} }
if err := gamesFile.Close(); err != nil { if closeError := file.Close(); (closeError != nil) && (updateError == nil) {
return err updateError = closeError
} }
if writeError != nil { if updateError != nil {
return writeError return updateError
} }
} else { } else {
return gamesFileError return updateError
} }
return nil return nil
} }
func (game *Game) RenameMod(modName string, newName string) error { func (game *Game) RenameMod(modName string, newName string) error {
if _, exists := game.Mods[modName]; !(exists) { var mod, exists = game.Mods[modName]
if !(exists) {
return fmt.Errorf("no mod with that name exists") return fmt.Errorf("no mod with that name exists")
} }
@ -524,59 +445,134 @@ func (game *Game) RenameMod(modName string, newName string) error {
return fmt.Errorf("a mod with the new name already exists") return fmt.Errorf("a mod with the new name already exists")
} }
var modPath, modPathError = game.configPath(modName) var modsDirPath = filepath.Join(configPath(), game.Name)
if modPathError != nil { if renameError := os.Rename(filepath.Join(modsDirPath, modName),
return modPathError filepath.Join(modsDirPath, newName)); renameError != nil {
}
var newPath, newPathError = game.configPath(modName)
if newPathError != nil {
return newPathError
}
if renameError := os.Rename(modPath, newPath); renameError != nil {
return renameError return renameError
} }
var mod = game.Mods[modName]
game.Mods[newName] = mod game.Mods[newName] = mod
delete(game.Mods, modName) delete(game.Mods, modName)
return game.saveMods()
}
func cachePath() string {
return fallbackPath(os.UserCacheDir, "cache")
}
func configPath() string {
return fallbackPath(os.UserConfigDir, "config")
}
func fallbackPath(getFalliblePath func() (string, error), fallbackSubdir string) string {
var path, pathError = getFalliblePath()
if pathError != nil {
// Fallback to homedir.
path, pathError = os.UserHomeDir()
if pathError != nil {
// User home dir should exist / be accessible.
panic(pathError)
}
return filepath.Join(path, "modman", fallbackSubdir)
}
return filepath.Join(path, "modman")
}
func (game *Game) saveDeployment() error {
var listPath = filepath.Join(cachePath(), "deployed.txt")
if len(game.DeployedFilePaths) == 0 {
return os.Truncate(listPath, 0)
}
if file, updateError := os.Create(listPath); updateError == nil {
var writer = bufio.NewWriter(file)
for _, filePath := range game.DeployedFilePaths {
if _, printError := fmt.Fprintln(writer, filePath); printError != nil {
updateError = printError
break
}
}
if flushError := writer.Flush(); flushError != nil && updateError == nil {
updateError = flushError
}
if syncError := file.Sync(); syncError != nil && updateError == nil {
updateError = syncError
}
if closeError := file.Close(); closeError != nil && updateError == nil {
updateError = closeError
}
if updateError != nil {
return updateError
}
} else {
return updateError
}
return nil return nil
} }
func (game *Game) cachePath(path string) (string, error) { func (game *Game) saveMods() error {
var dirPath, pathError = os.UserCacheDir() var file, updateError = os.Open(filepath.Join(configPath(), game.Name, "mods.ini"))
if pathError != nil { if updateError != nil {
return "", pathError return updateError
} }
dirPath = filepath.Join(dirPath, "modman", game.Name) var writer = bufio.NewWriter(file)
var builder = ini.NewBuilder(writer)
if mkdirError := os.MkdirAll(dirPath, 0755); mkdirError != nil { for name, mod := range game.Mods {
return "", mkdirError if buildError := builder.Section(name); buildError != nil {
updateError = buildError
break
}
if buildError := builder.KeyValue("format", mod.Format); buildError != nil {
updateError = buildError
break
}
if buildError := builder.KeyValue("source", mod.Source); buildError != nil {
updateError = buildError
break
}
if buildError := builder.KeyValue("version", mod.Version); buildError != nil {
updateError = buildError
break
}
} }
return filepath.Join(dirPath, path), nil if writeError := writer.Flush(); (writeError != nil) && (updateError == nil) {
} updateError = writeError
}
func (game *Game) configPath(path string) (string, error) {
var dirPath, pathError = os.UserConfigDir() if syncError := file.Sync(); (syncError != nil) && (updateError == nil) {
updateError = syncError
if pathError != nil { }
return "", pathError
} if closeError := file.Close(); (closeError != nil) && (updateError == nil) {
updateError = closeError
dirPath = filepath.Join(dirPath, "modman", game.Name) }
if mkdirError := os.MkdirAll(dirPath, 0755); mkdirError != nil { return updateError
return "", mkdirError
}
return filepath.Join(dirPath, path), nil
} }