package steam import ( "archive/tar" "io" "os" "path/filepath" "strings" ) const ( defaultDirectoryMode = 0775 defaultFileMode = 0644 ) // Package writes the package to wr, returning errors if any func (l *Library) Package(game string, wr io.WriteCloser) error { g, ok := l.games[game] if !ok { return E_GameDoesNotExist } j := newJob("package", g) defer j.done() l.status.addJob(j) acf, err := FindACF(l.folder, g.Name) if err != nil { j.addError(err) return err } twriter := tar.NewWriter(wr) paths := []string{ filepath.Join(l.folder, "common", g.Name), acf, } for _, pth := range paths { err := filepath.Walk(pth, tarWalkfn(twriter, l.folder)) if err != nil { j.addError(err) return err } } err = twriter.Flush() if err != nil { j.addError(err) return err } err = twriter.Close() if err != nil { j.addError(err) return err } err = wr.Close() if err != nil { j.addError(err) } return err } // Extract will read a tarball from the io.Reader and install the game into // the current library path. This offers no visibility into the progress, // as it does not update the job status on the progress, though it will // populate errors. // // Most callers will want to use ExtractHTTP or ExtractFile instead func (l *Library) Extract(r io.Reader) (*Game, error) { g := &Game{LibraryPath: l.folder} j := newJob("extract", g) defer j.done() l.status.addJob(j) return l.extractPrimitive(j, g, r) } func (l *Library) extractPrimitive(j *Job, g *Game, r io.Reader) (*Game, error) { treader := tar.NewReader(r) for { hdr, err := treader.Next() if err == io.EOF { // We've reached the end! Whoee break } if err != nil { j.addError(err) return nil, err } fileName := filepath.ToSlash(hdr.Name) if g.Name == "" { s := strings.Split(fileName, "/") if len(s) >= 2 { g.Name = s[1] } } fileName = filepath.Join(l.folder, fileName) info := hdr.FileInfo() if info.IsDir() { // I don't like hard-coded permissions but it // it helps with overall platform compatibility err = os.MkdirAll(fileName, defaultDirectoryMode) if err != nil { j.addError(err) return nil, err } continue } err = os.MkdirAll(filepath.Dir(fileName), defaultDirectoryMode) if err != nil { j.addError(err) return nil, err } // Create a file handle to work with f, err := os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFileMode) if err != nil { j.addError(err) return nil, err } if _, err := io.Copy(f, treader); err != nil { j.addError(err) f.Close() return nil, err } f.Close() } err := g.SetSizeInfo() if err != nil { j.addError(err) return nil, err } l.m.Lock() l.games[g.Name] = g l.m.Unlock() return g, nil } // Delete removes all of the game files and the ACF func (l *Library) Delete(game string) error { g, ok := l.games[game] if !ok { return E_GameDoesNotExist } j := newJob("delete", g) defer j.done() l.status.addJob(j) acf, err := FindACF(l.folder, game) if err != nil { j.addError(err) return err } if err := os.Remove(acf); err != nil { j.addError(err) return err } err = os.RemoveAll(filepath.Join(l.folder, "common", g.Name)) if err != nil { j.addError(err) return err } l.m.Lock() delete(l.games, game) l.m.Unlock() return nil }