From c202f2eca32e1ab2e313417168351df1c58ee062 Mon Sep 17 00:00:00 2001 From: Mitch Riedstra Date: Wed, 4 Aug 2021 23:53:36 -0400 Subject: More major changes. Web UI works. Downloading games works. Status works. extractFile needs work --- steam/extract.go | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 2 deletions(-) (limited to 'steam/extract.go') diff --git a/steam/extract.go b/steam/extract.go index f73d5f7..9a7a930 100644 --- a/steam/extract.go +++ b/steam/extract.go @@ -1,15 +1,90 @@ package steam import ( + "archive/tar" "errors" "fmt" "io" + "net/url" + "os" + "path/filepath" + "strings" "time" ) // how often are we going to be updating our status information? const updateEveryNBytes = 10 * 1024 * 1024 // 10mb +// ExtractSmart attempts to discover what kind of resource is behind uri +// and extract it appropriately. It may fail with E_BadURI. +// +// For example the following forms are accepted: +// +// ExtractSmart("http://127.0.0.1/some-archive") +// ExtractSmart("https://example.com/some-archive") +// ExtractSmart("file:///some/local/file/path/to/archive.tar") +// ExtractSmart("/direct/path/to/archive.tar") +// ExtractSmart("C:\Users\user\Downloads\archive.tar") +func (l *Library) ExtractSmart(uri string) (*Game, error) { + if strings.HasPrefix(uri, "http") { + _, err := url.Parse(uri) + if err == nil { + return l.ExtractHTTP(uri) + } + } else if strings.HasPrefix(uri, "file") { + u, err := url.Parse(uri) + if err == nil { + return l.ExtractFile(u.Path) + } + } else if _, err := os.Stat(uri); err == nil { + return l.ExtractFile(uri) + } + + return nil, E_BadURI +} + +// ExtractFile is a wrapper around Extract that handles an HTTP endpoint. +// this spawns an "extractFile" on the library. Status will be updated there +// as this goes along. Non fatal and fatal errors will be populated there +func (l *Library) ExtractFile(fn string) (*Game, error) { + g := &Game{} + j := newJob("extractFile", g) + defer j.done() + + l.status.addJob(j) + + fi, err := os.Stat(fn) + if err != nil { + j.addError(err) + return g, err + } + j.setSize(fi.Size()) + + fh, err := os.Open(fn) + if err != nil { + j.addError(err) + return g, err + } + + return l.extractUpdate(j, g, fh) +} + +// 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) +} + // extractUpdate takes care of updating the job as it goes along at updateEveryNBytes // it will be reported back to the Job's status. func (l *Library) extractUpdate(j *Job, g *Game, rdr io.ReadCloser) (*Game, error) { @@ -43,7 +118,8 @@ func (l *Library) extractUpdate(j *Job, g *Game, rdr io.ReadCloser) (*Game, erro j.setTransferred(total) // rate in bytes/sec - rate := total / int64(time.Since(*j.StartTime()).Seconds()) + // rate := total / int64(time.Since(*j.StartTime()).Seconds()) + rate := float64(total) / float64(time.Since(*j.StartTime()).Seconds()) estSize := j.GetSize() @@ -52,7 +128,8 @@ func (l *Library) extractUpdate(j *Job, g *Game, rdr io.ReadCloser) (*Game, erro continue } - remaining := *estSize - total + // remaining := *estSize - total + remaining := float64(*estSize - total) j.setETA(time.Duration((remaining / rate) / 1000 / 1000 / 1000)) } @@ -64,3 +141,76 @@ func (l *Library) extractUpdate(j *Job, g *Game, rdr io.ReadCloser) (*Game, erro return g, err } + +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 +} -- cgit v1.2.3