Skip to content

Commit

Permalink
Add compatdata purge command
Browse files Browse the repository at this point in the history
  • Loading branch information
nning committed Apr 1, 2022
1 parent 64bef08 commit 84cf8c2
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 19 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ protonutils is a CLI tool that provides different utilities to make using the [P
* Print or open compatdata and install directories by game name
(handy of you want to mess with savegames or mods, for example)
* Update assigned compatibility tool for one or more games
* Clean-up unused `compatdata` directories

The commands `list`, `appid`, `compatdata`, `installdir`, and `compattool` do only work with (non-native) games that either have an explicit Proton/CompatTool mapping or have been started at least once with Proton.

Expand Down
108 changes: 89 additions & 19 deletions cmd/protonutils/compatdata.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package main

import (
"errors"
"fmt"
"os"
"os/exec"
"path"
"strings"

"github.com/dustin/go-humanize"
"github.com/nning/protonutils/steam"
"github.com/nning/protonutils/utils"
"github.com/spf13/cobra"
)

Expand All @@ -32,6 +33,14 @@ var compatdataOpenCmd = &cobra.Command{
Run: compatdataOpen,
}

var compatdataPurgeCmd = &cobra.Command{
Use: "purge",
Short: "Purge unused compatdata directories",
Long: "Purge leftover compatdata directories of previously installed and now uninstalled games",
Args: cobra.MinimumNArgs(0),
Run: compatdataPurge,
}

func init() {
rootCmd.AddCommand(compatdataCmd)

Expand All @@ -44,27 +53,18 @@ func init() {
compatdataOpenCmd.Flags().StringVarP(&user, "user", "u", "", "Steam user name (or SteamID3)")
compatdataOpenCmd.Flags().BoolVarP(&ignoreCache, "ignore-cache", "c", false, "Ignore app ID/name cache")
compatdataOpenCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Show app name")

compatdataCmd.AddCommand(compatdataPurgeCmd)
compatdataPurgeCmd.Flags().StringVarP(&user, "user", "u", "", "Steam user name (or SteamID3)")
compatdataPurgeCmd.Flags().BoolVarP(&ignoreCache, "ignore-cache", "c", false, "Ignore app ID/name cache")
compatdataPurgeCmd.Flags().BoolVarP(&yes, "yes", "y", false, "Do not ask")
}

func getCompatdataPath(idOrName string) (string, string, error) {
func compatdataPath(cmd *cobra.Command, args []string) {
s, err := steam.New(user, cfg.SteamRoot, ignoreCache)
exitOnError(err)

id, name, err := s.GetAppIDAndName(idOrName)
if err != nil {
return "", "", err
}

p := s.LibraryConfig.GetLibraryPathByID(id)
if p == "" {
exitOnError(errors.New("Game not installed"))
}

return path.Join(p, "steamapps", "compatdata", id), name, nil
}

func compatdataPath(cmd *cobra.Command, args []string) {
p, n, err := getCompatdataPath(strings.Join(args, " "))
p, n, err := s.GetCompatdataPath(strings.Join(args, " "))
exitOnAmbiguousNameError(cmd, args, err)

if verbose {
Expand All @@ -75,7 +75,10 @@ func compatdataPath(cmd *cobra.Command, args []string) {
}

func compatdataOpen(cmd *cobra.Command, args []string) {
p, n, err := getCompatdataPath(strings.Join(args, " "))
s, err := steam.New(user, cfg.SteamRoot, ignoreCache)
exitOnError(err)

p, n, err := s.GetCompatdataPath(strings.Join(args, " "))
exitOnAmbiguousNameError(cmd, args, err)

if verbose {
Expand All @@ -85,3 +88,70 @@ func compatdataOpen(cmd *cobra.Command, args []string) {
_, err = exec.Command("xdg-open", p).Output()
exitOnError(err)
}

func compatdataPurge(cmd *cobra.Command, args []string) {
s, err := steam.New(user, cfg.SteamRoot, ignoreCache)
exitOnError(err)

err = s.ReadCompatTools()
exitOnError(err)

fmt.Println("Calculating unused compatdata directory sizes...")

type entry struct{
name string
path string
size uint64
}
var games []entry
var total uint64

for _, tool := range s.CompatTools {
for _, game := range tool.Games {
if game.IsInstalled {
continue
}

p := s.SearchCompatdataPath(game.ID)
if p == "" {
continue
}

size, err := utils.DirSize(p)
exitOnError(err)

games = append(games, entry{game.Name, p, size})
}
}

if len(games) > 0 {
fmt.Println()
} else {
fmt.Println("No unused compatdata directories found!")
os.Exit(0)
}

for _, entry := range games {
total += entry.size
fmt.Printf("%10v %v\n", humanize.Bytes(entry.size), entry.name)
}

fmt.Printf("\nTotal size: %v\n", humanize.Bytes(total))
fmt.Println("WARNING: Backup save game data for games without Steam Cloud support!")

if !yes {
isOK, err := utils.AskYesOrNo("Do you want to delete compatdata directories?")
exitOnError(err)

if !isOK {
fmt.Println("Aborted")
return
}
}

for _, entry := range games {
os.RemoveAll(entry.path)
}

fmt.Println("Done")
}
16 changes: 16 additions & 0 deletions steam/LibraryConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ type LibraryConfigVdf struct {
Vdf
}

func (vdf LibraryConfigVdf) GetLibraryPaths() []string {
var paths []string

x := vdf.Root.FirstSubTree()
for {
paths = append(paths, x.FirstByName("path").String())

x = x.NextSubTree()
if x == nil {
break
}
}

return paths
}

// GetLibraryPathByID returns library path for app id
func (vdf LibraryConfigVdf) GetLibraryPathByID(id string) string {
x := vdf.Root.FirstSubTree()
Expand Down
43 changes: 43 additions & 0 deletions steam/compatdata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package steam

import (
"errors"
"os"
"path"
)

// GetCompatdataPath returns compatdata path and game name for given game ID or
// name
func (s *Steam) GetCompatdataPath(idOrName string) (string, string, error) {
id, name, err := s.GetAppIDAndName(idOrName)
if err != nil {
return "", "", err
}

p := s.LibraryConfig.GetLibraryPathByID(id)
if p == "" {
return "", "", errors.New("Game not installed")
}

return path.Join(p, "steamapps", "compatdata", id), name, nil
}

// SearchCompatdataPath searches for compatdata path for given game id in all
// library paths
func (s *Steam) SearchCompatdataPath(id string) string {
paths := s.LibraryConfig.GetLibraryPaths()
for _, p := range paths {
x := path.Join(p, "steamapps", "compatdata", id)

info, err := os.Stat(x)
if err != nil {
continue
}

if info.IsDir() {
return x
}
}

return ""
}
25 changes: 25 additions & 0 deletions utils/dir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package utils

import (
"os"
"path/filepath"
)

// DirSize retusn total size of directory
func DirSize(path string) (uint64, error) {
var size uint64

err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}

if !info.IsDir() {
size += uint64(info.Size())
}

return err
})

return size, err
}

0 comments on commit 84cf8c2

Please sign in to comment.