aboutsummaryrefslogtreecommitdiff
path: root/steam/extract.go
diff options
context:
space:
mode:
authorMitch Riedstra <mitch@riedstra.us>2021-08-04 23:53:36 -0400
committerMitch Riedstra <mitch@riedstra.us>2021-08-04 23:53:36 -0400
commitc202f2eca32e1ab2e313417168351df1c58ee062 (patch)
tree6540629b337d2d769581baec26096ac0555f71f9 /steam/extract.go
parent742938b00222c7ad57ad11eb24850d9202c2503d (diff)
downloadsteam-export-c202f2eca32e1ab2e313417168351df1c58ee062.tar.gz
steam-export-c202f2eca32e1ab2e313417168351df1c58ee062.tar.xz
More major changes. Web UI works. Downloading games works. Status works. extractFile needs work
Diffstat (limited to 'steam/extract.go')
-rw-r--r--steam/extract.go154
1 files changed, 152 insertions, 2 deletions
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
+}