aboutsummaryrefslogtreecommitdiff
path: root/steam/steam.go
blob: 66ef7f4b95a1183e6b75339fa24def0079b1fe42 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Package steam is designed to be a rather simplistic library to help pull
// information from a local steam library and run basic operations like
// archiving, restore and deleting games
package steam

import (
	"errors"
	"fmt"
	"io/ioutil"
	"regexp"
	"sync"
)

var E_GameDoesNotExist = errors.New("Game does not exist")

// Library is used to represent the steam library, the Games map is populated
// by NewLibrary when called or when ProcessLibrary is called directly
// The key in the map is the same as the Game's `Name` property.
//
// Status contains various bits of information about previous jobs
// and the status of any currently running jobs
type Library struct {
	folder string
	games  map[string]*Game
	status *Jobs

	m sync.Mutex
}

// Game represents an actual game in the steam Library. The purpose is only
// to provide info on a game.
type Game struct {
	Name        string
	LibraryPath string
	Size        int64
}

var slugregexp = regexp.MustCompile(`[^-0-9A-Za-z_:.]`)

// Slug returns a safer version of the name with spaces and other chars
// transformed into dashes for use in HTML element ids and such.
func (g Game) Slug() string {
	return slugregexp.ReplaceAllString(g.Name, "-")
}

// NewLibrary returns a pointer to a processed library and an error
// if any
func NewLibrary(path string) (*Library, error) {
	l := &Library{
		status: &Jobs{
			running:  make([]*Job, 0),
			previous: make([]*Job, 0),
		},
	}
	err := l.ProcessLibrary(path)
	if err != nil {
		return nil, err
	}
	return l, err
}

// NewLibraryMust is the same as NewLibrary but calls panic if there
// is any error
func NewLibraryMust(path string) *Library {
	l, err := NewLibrary(path)
	if err != nil {
		panic(err)
	}
	return l
}

// Games returns a slice of *Game for the current library
func (l *Library) Games() []*Game {
	l.m.Lock()
	out := []*Game{}
	for _, g := range l.games {
		out = append(out, g)
	}
	l.m.Unlock()

	return out
}

// Jobs returns the current *Jobs struct which can be used to keep track
// of any long running operations on the library as well as any errors
// encountered along the way
func (l *Library) Status() *Jobs {
	return l.status
}

// ProcessLibrary Populates the "Folder" and "Games" fields based on the
// provided directory.
//
func (s *Library) ProcessLibrary(r string) error {
	if s.status.Running() {
		return errors.New("Cannot process library with actions running")
	}

	if !hasCommon(r) {
		return fmt.Errorf("No common directory in: %s", r)
	}

	s.m.Lock()
	defer s.m.Unlock()
	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() {
			g := &Game{
				Name:        f.Name(),
				LibraryPath: r,
			}
			g.SetSizeInfo()

			s.games[f.Name()] = g
		}
	}

	return nil
}

func (s *Library) String() (str string) {
	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
}

func hasCommon(d string) bool {
	dirs, err := ioutil.ReadDir(d)
	if err != nil {
		return false
	}
	for _, f := range dirs {
		if f.Name() == "common" && f.IsDir() {
			return true
		}
	}
	return false
}