From 891447f0e40a6d1a9637b3333cffed8eb2543c51 Mon Sep 17 00:00:00 2001 From: Mitch Riedstra Date: Fri, 20 Nov 2020 22:46:45 -0500 Subject: Works after reorganization. --- archive/archive.go | 5 +- cmd/cli/main.go | 133 ++++++++++++++++++----------------------------------- cmd/cli/unix.go | 10 ++++ cmd/cli/windows.go | 5 ++ steam/archive.go | 55 ++++++++++++++++++++++ steam/package.go | 112 ++++++++++++++++++++++---------------------- steam/steam.go | 62 +++++++++++++------------ steam/unix.go | 10 ---- steam/windows.go | 5 -- 9 files changed, 206 insertions(+), 191 deletions(-) create mode 100644 cmd/cli/unix.go create mode 100644 cmd/cli/windows.go create mode 100644 steam/archive.go delete mode 100644 steam/unix.go delete mode 100644 steam/windows.go diff --git a/archive/archive.go b/archive/archive.go index 639a864..313c062 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -47,7 +47,7 @@ func (a *Archive) Tar(compressionType string) error { defer twriter.Close() for _, v := range a.Input { - if err := filepath.Walk(v, tarWalkfn(twriter)); err != nil { + if err := filepath.Walk(v, TarWalkfn(twriter)); err != nil { return err } } @@ -56,8 +56,7 @@ func (a *Archive) Tar(compressionType string) error { } -func tarWalkfn(writer *tar.Writer) filepath.WalkFunc { - // This is an interesting trick to get around scoping issues +func TarWalkfn(writer *tar.Writer) filepath.WalkFunc { return func(path string, info os.FileInfo, err error) error { if err != nil { return err diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 3292d30..989592a 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -6,16 +6,9 @@ import ( "fmt" "os" - "riedstra.dev/mitch/steam-export/config" "riedstra.dev/mitch/steam-export/steam" ) -var ( - errorHandling flag.ErrorHandling = flag.ExitOnError - - steamLib *steam.Library = &steam.Library{} -) - func parseArgs(args []string) error { if len(args) < 2 { return errors.New("Not enough arguments") @@ -30,8 +23,6 @@ func parseArgs(args []string) error { return extractGame(aa) case "delete": return deleteGame(aa) - case "edit-config": - return editConfig(aa) default: printHelp() } @@ -49,34 +40,21 @@ Subcommands: extract delete server - edit-config Type in a subcommand -h or -help for more information `) } -func editConfig(args []string) error { - fl := flag.NewFlagSet("edit-config", errorHandling) - e := fl.String("e", "", "Editor to invoke") - if err := fl.Parse(args); err != nil { - return err - } - return config.EditDefaultConfig(*e) -} - func listGames(args []string) error { - fl := flag.NewFlagSet("list", errorHandling) - lib := fl.String("l", steam.DefaultLib, + fl := flag.NewFlagSet("list", flag.ExitOnError) + lib := fl.String("l", DefaultLib, "Path to library in question. All the way to the 'steamapps' folder") - l := steam.DefaultLib fl.Parse(args) - if fl.Parsed() { - l = string(*lib) - } - if err := steamLib.ProcessLibrary(l); err != nil { + steamLib := &steam.Library{} + if err := steamLib.ProcessLibrary(*lib); err != nil { return err } @@ -86,111 +64,90 @@ func listGames(args []string) error { } func packageGame(args []string) error { - fl := flag.NewFlagSet("package", errorHandling) - lib := fl.String("l", steam.DefaultLib, + fl := flag.NewFlagSet("package", flag.ExitOnError) + libPth := fl.String("l", DefaultLib, "Path to library in question. All the way to the 'steamapps' folder") - fileName := fl.String("f", "export", - "Name of the archive file to be created. Please do not include the file extension") - game := fl.Int("g", -1, - "Index of the game to be exported. Please see `list` for the index") - compress := fl.String("z", "gz", - "Compression type. Default 'gz' '' is no compression") - - var g int - l := steam.DefaultLib + fileName := fl.String("f", "", "Name of archive to be created") + game := fl.String("g", "", "Name of the game to be exported.") + fl.Parse(args) - if fl.Parsed() { - l = string(*lib) - g = int(*game) + + if *fileName == "" { + return errors.New("You need to specify a file name") } - if err := steamLib.ProcessLibrary(l); err != nil { + lib := &steam.Library{} + if err := lib.ProcessLibrary(*libPth); err != nil { return err } - if len(steamLib.Games) < g || g == -1 { - return errors.New("Cannot find game for index provided or no index provided") + G, ok := lib.Games[*game] + if !ok { + return errors.New("Game does not exist") } - if *fileName == "export" { - fileName = &steamLib.Games[g] + f, err := os.OpenFile(*fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0664) + if err != nil { + return err } + defer f.Close() - switch *compress { - case "gz": - return steamLib.PackageGameToFile(g, *fileName+".tar.gz", *compress) - default: - return steamLib.PackageGameToFile(g, *fileName+".tar", *compress) + err = G.Package(f) + if err != nil { + return err } + return nil } func extractGame(args []string) error { - fl := flag.NewFlagSet("extract", errorHandling) - lib := fl.String("l", steam.DefaultLib, + fl := flag.NewFlagSet("extract", flag.ExitOnError) + libPath := fl.String("l", DefaultLib, "Path to library in question. All the way to the 'steamapps' folder") fileName := fl.String("f", "", - "Name of the archive file to be extracted. Please include the file extension") - compress := fl.String("z", "gz", - "Compression type. Default 'gz' '' is no compression") + "Name of the archive file to be extracted. Include the file extension") - l := steam.DefaultLib fl.Parse(args) - if fl.Parsed() { - l = string(*lib) - } - if err := steamLib.ProcessLibrary(l); err != nil { + lib := &steam.Library{} + + if err := lib.ProcessLibrary(*libPath); err != nil { return err } - if *fileName == "" { - return errors.New("No filename provided") + fh, err := os.Open(*fileName) + if err != nil { + return err } - return steamLib.ExtractGameFromFile(*fileName, *compress) - + return lib.Extract(fh) } func deleteGame(args []string) error { - fl := flag.NewFlagSet("delete", errorHandling) - lib := fl.String("l", steam.DefaultLib, + fl := flag.NewFlagSet("delete", flag.ExitOnError) + libPath := fl.String("l", DefaultLib, "Path to library in question. All the way to the 'steamapps' folder") - game := fl.Int("g", -1, - "Index of the game to be deleted. Please see `list` for the index") + game := fl.String("g", "", "Name of the game to be deleted.") - var g int - l := steam.DefaultLib fl.Parse(args) - if fl.Parsed() { - l = string(*lib) - g = int(*game) - } - if err := steamLib.ProcessLibrary(l); err != nil { + lib := &steam.Library{} + + if err := lib.ProcessLibrary(*libPath); err != nil { return err } - if len(steamLib.Games) < g || g == -1 { - return errors.New("Cannot find game for index provided or no index provided") + G, ok := lib.Games[*game] + if !ok { + return errors.New("Game does not exist") } - return steamLib.DeleteGame(g) + return G.Delete() } func main() { - config, err := config.LoadConfig() - if err != nil { - fmt.Println(err) - } else { - if config.DefaultRepository != "" { - steam.DefaultLib = config.DefaultRepository - } - } - if err := parseArgs(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) printHelp() } } - diff --git a/cmd/cli/unix.go b/cmd/cli/unix.go new file mode 100644 index 0000000..6b1b1ae --- /dev/null +++ b/cmd/cli/unix.go @@ -0,0 +1,10 @@ +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris + +package main + +import ( + "os" + "path/filepath" +) + +var DefaultLib string = filepath.Join(os.Getenv("HOME"), ".steam/steam/steamapps") diff --git a/cmd/cli/windows.go b/cmd/cli/windows.go new file mode 100644 index 0000000..2effa08 --- /dev/null +++ b/cmd/cli/windows.go @@ -0,0 +1,5 @@ +// +build windows + +package main + +var DefaultLib string = `C:\Program Files (x86)\Steam\steamapps` diff --git a/steam/archive.go b/steam/archive.go new file mode 100644 index 0000000..d997051 --- /dev/null +++ b/steam/archive.go @@ -0,0 +1,55 @@ +package steam + +import ( + "archive/tar" + "io" + "os" + "path/filepath" + "strings" +) + +func TarWalkfn(writer *tar.Writer) filepath.WalkFunc { + return func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + // Convert Windows paths to Unix paths + path = strings.Replace(path, "\\", "/", -1) + + // TODO; See if tar.FileInfoheader() could be used instead + // without the pathing issues I encountered + h := &tar.Header{ + Name: path, + Size: info.Size(), + // I don't like it... but it helps with platform compatibility + Mode: 0664, + ModTime: info.ModTime(), + } + + err = writer.WriteHeader(h) + if err != nil { + return err + } + + _, err = io.Copy(writer, f) + if err != nil { + // TODO: Figure out how to add more useful information to + // These errors + // fmt.Fprintln(os.Stderr, f.Name()) + return err + } + + return nil + } +} diff --git a/steam/package.go b/steam/package.go index a73a6bc..9000cba 100644 --- a/steam/package.go +++ b/steam/package.go @@ -1,94 +1,96 @@ package steam import ( - // "fmt" - "riedstra.dev/mitch/steam-export/archive" + "archive/tar" + "io" "os" "path/filepath" + "strings" ) -func (l *Library) PackageGameToFile(index int, file, compress string) error { - g := l.Games[index] - - working_dir, err := os.Getwd() - if err != nil { +// Package writes the package, returning bytes written and an error if any +func (g *Game) Package(wr io.Writer) error { + if err := os.Chdir(g.LibraryPath); err != nil { return err } - // output := working_dir + "/" + g + ".tar" - output, err := filepath.Abs(file) + acf, err := FindACF(g.LibraryPath, g.Name) if err != nil { return err } - os.Chdir(l.Folder) - acf, err := l.FindACF(g) - if err != nil { - return err - } - input := []string{"common/" + g, acf} - a := archive.Archive{Output: output, Input: input} - err = a.Tar(compress) - if err != nil { - return err - } + twriter := tar.NewWriter(wr) - os.Chdir(working_dir) + for _, pth := range []string{"common/" + g.Name, acf} { + if err := filepath.Walk(pth, TarWalkfn(twriter)); err != nil { + return err + } + } return nil } -func (l *Library) ExtractGameFromFile(f, compress string) error { - working_dir, err := os.Getwd() - if err != nil { +func (l *Library) Extract(r io.Reader) error { + if err := os.Chdir(l.Folder); err != nil { return err } - f, err = filepath.Abs(f) - if err != nil { - return err - } + treader := tar.NewReader(r) - if err = os.Chdir(l.Folder); err != nil { - return err - } - u := &archive.Unarchive{ - Input: f, - } - if err := u.UnTar(compress); err != nil { - return err - } + for { + hdr, err := treader.Next() + if err == io.EOF { + // We've reached the end! Whoee + break + } + if err != nil { + return err + } - if err = os.Chdir(working_dir); err != nil { - return err - } - return nil -} + // Fix windows slashes... + fileName := strings.Replace(hdr.Name, "\\", "/", -1) -func (l *Library) DeleteGame(i int) error { - g := l.Games[i] + info := hdr.FileInfo() + if info.IsDir() { + // I don't like hard-coded permissions but it + // it helps with overall platform compatibility + if err = os.MkdirAll(fileName, 0775); err != nil { + return err + } + continue + } + + if err = os.MkdirAll(filepath.Dir(fileName), 0775); err != nil { + return err + } + + // Create a file handle to work with + f, err := os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0664) + if err != nil { + return err + } + defer f.Close() + if _, err := io.Copy(f, treader); err != nil { + return err + } - working_dir, err := os.Getwd() - if err != nil { - return err } - if err = os.Chdir(l.Folder); err != nil { + return nil +} + +func (g *Game) Delete() error { + if err := os.Chdir(g.LibraryPath); err != nil { return err } - acf, err := l.FindACF(g) + acf, err := FindACF(g.LibraryPath, g.Name) if err != nil { return err } - // fmt.Fprintf(os.Stderr, "Removing %q %q\n", acf, "common/"+g) if err := os.Remove(acf); err != nil { return err } - if err := os.RemoveAll("common/" + g); err != nil { - return err - } - - if err = os.Chdir(working_dir); err != nil { + if err := os.RemoveAll("common/" + g.Name); err != nil { return err } diff --git a/steam/steam.go b/steam/steam.go index ea27e5a..7071a20 100644 --- a/steam/steam.go +++ b/steam/steam.go @@ -5,19 +5,21 @@ package steam import ( "fmt" "io/ioutil" - "errors" - "bufio" "os" "path/filepath" "strings" - // "log" ) type Library struct { Folder string - Games []string + Games map[string]Game +} + +type Game struct { + Name string + LibraryPath string } func ProcessMultipleLibraries(r []string) ([]*Library, error) { @@ -35,30 +37,33 @@ func ProcessMultipleLibraries(r []string) ([]*Library, error) { // Populate the "Folder" and "Games" fields based on the provided directory func (s *Library) ProcessLibrary(r string) error { - if hasCommon(r) { - dirs, err := ioutil.ReadDir(r + "/common") - if err != nil { - return err - } - s.Folder = r - for _, f := range dirs { - if f.IsDir() { - s.Games = append(s.Games, f.Name()) + if !hasCommon(r) { + return errors.New(fmt.Sprintf("No common directory in: %s", r)) + } + + s.Games = make(map[string]Game) + + dirs, err := ioutil.ReadDir(r + "/common") + if err != nil { + return err + } + s.Folder = r + for _, f := range dirs { + if f.IsDir() { + s.Games[f.Name()] = Game{ + Name: f.Name(), + LibraryPath: r, } + // s.Games = append(s.Games, f.Name()) } - } else { - return errors.New(fmt.Sprintf("No common directory in: %s", r)) } + return nil } // Find the ACF files related to this video game -func (l *Library) FindACF(g string) (string, error) { - working_dir, err := os.Getwd() - if err != nil { - return "", err - } - if err = os.Chdir(l.Folder); err != nil { +func FindACF(libraryPath, game string) (string, error) { + if err := os.Chdir(libraryPath); err != nil { return "", err } files, err := filepath.Glob("*.acf") @@ -84,8 +89,7 @@ func (l *Library) FindACF(g string) (string, error) { scanner := bufio.NewScanner(f) for scanner.Scan() { // Finally check and see if the file has the video game name - if strings.Contains(scanner.Text(), g) { - os.Chdir(working_dir) + if strings.Contains(scanner.Text(), game) { return fn, nil // fmt.Printf("%s/%s:%d: %s\n", root, path, i, scanner.Text()) } @@ -93,18 +97,16 @@ func (l *Library) FindACF(g string) (string, error) { } str := "Couldn't find ACF file related to Game: %s" - return "", errors.New(fmt.Sprintf(str, g)) + return "", errors.New(fmt.Sprintf(str, game)) } // This is automatically called to print out the contents of the struct // when things like fmt.Println are used func (s *Library) String() (str string) { - str = fmt.Sprintln("\n----") - str = str + fmt.Sprintln(s.Folder) - str = str + "\n----\n" - for k, v := range s.Games { - str = str + fmt.Sprintf("%d: %s\n", k, v) - //str = str + fmt.Sprintln(v) + str = fmt.Sprintf("Library: %s\n", s.Folder) + str = str + "----\n" + for _, v := range s.Games { + str = str + fmt.Sprintf("%s\n", v.Name) } return } diff --git a/steam/unix.go b/steam/unix.go deleted file mode 100644 index caa4b43..0000000 --- a/steam/unix.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris - -package steam - -import ( - "os" - "path/filepath" -) - -var DefaultLib string = filepath.Join(os.Getenv("HOME"), ".steam/steam/steamapps") diff --git a/steam/windows.go b/steam/windows.go deleted file mode 100644 index 17fe62c..0000000 --- a/steam/windows.go +++ /dev/null @@ -1,5 +0,0 @@ -// +build windows - -package steam - -var DefaultLib string = `C:\Program Files (x86)\Steam\steamapps` -- cgit v1.2.3