package steam import ( "encoding/json" "sync" "time" "fmt" "os" ) var debuglogging = false func debugLogJob(s string, args ...interface{}) { if debuglogging { fmt.Fprintf(os.Stderr, s, args...) } } func debugLogJobs(s string, args ...interface{}) { if debuglogging { fmt.Fprintf(os.Stderr, s, args...) } } // JobStatus provides specific information about an individual job type Job struct { action string target *Game running bool start *time.Time errors []error // If applicablle size *int64 transferred *int64 eta *time.Duration m sync.Mutex } func (j Job) MarshalJSON() ([]byte, error) { return json.Marshal( struct { Action string `json:"action"` Target *Game `json:"Target"` Running bool `json:"Running"` Start *time.Time `json:"Start"` Errors []error `json:"Errors"` // If applicablle Size *int64 `json:"Size"` Transferred *int64 `json:"Transferred"` Eta *time.Duration `json:"ETA"` }{ Action: j.action, Target: j.target, Running: j.running, Start: j.start, Errors: j.errors, Size: j.size, Transferred: j.transferred, Eta: j.eta, }) } // Action is a short string describing the action, i.e. "packaging", "deleting" func (j *Job) Action() string { j.m.Lock() defer j.m.Unlock() debugLogJob("Action on: '%s'\n", *j) return j.action } // Target returns the game that is the target of the action func (j *Job) Target() *Game { j.m.Lock() defer j.m.Unlock() debugLogJob("Target on: '%s'\n", *j) return j.target } // IsRunning returns true if the job is currently running, otherwise false func (j *Job) IsRunning() bool { j.m.Lock() defer j.m.Unlock() debugLogJob("IsRunning on: '%s'\n", *j) return j.running } // StartTime returns the time in which the job started func (j *Job) StartTime() *time.Time { j.m.Lock() defer j.m.Unlock() debugLogJob("StartTime on: '%s'\n", *j) return j.start } func (j *Job) Errors() []error { j.m.Lock() defer j.m.Unlock() debugLogJob("errors on: '%s'\n", *j) return j.errors } // newJob sets up a job of action for the target Game func newJob(action string, target *Game) *Job { debugLogJob("New job: '%s' target: '%s'\n", action, target) t := time.Now() return &Job{ action: action, target: target, running: true, start: &t, } } func (j *Job) setSize(size int64) { j.m.Lock() defer j.m.Unlock() debugLogJob("setSize on: '%s'\n", *j) j.size = &size } // GetSize returns the size set if applicable for the operation func (j *Job) GetSize() *int64 { j.m.Lock() defer j.m.Unlock() debugLogJob("getSize on: '%s'\n", *j) return j.size } func (j *Job) setTransferred(transferred int64) { j.m.Lock() defer j.m.Unlock() debugLogJob("setTransferred on: '%s'\n", *j) j.transferred = &transferred } // GetTransferred returns the transferred set if applicable for the operation func (j *Job) GetTransferred() *int64 { j.m.Lock() defer j.m.Unlock() debugLogJob("GetTransferred on: '%s'\n", *j) return j.transferred } // setETA sets the eta to the speicifed duration func (j *Job) setETA(d time.Duration) { j.m.Lock() defer j.m.Unlock() debugLogJob("setETA on: '%s'\n", *j) j.eta = &d } // GetETA returns the ETA to completion as a *time.Duration. nil represents // when func (j *Job) GetETA() *time.Duration { j.m.Lock() defer j.m.Unlock() debugLogJob("GetETA on: '%s'\n", *j) return j.eta } // done sets running to false func (j *Job) done() { j.m.Lock() defer j.m.Unlock() j.running = false } // addError appends an error to the internal slice of errors func (j *Job) addError(err error) { j.m.Lock() defer j.m.Unlock() debugLogJob("add error on: '%s'\n", *j) j.errors = append(j.errors, err) } // Jobs is by the Library to determine whether or not we currently have any // jobs running on this library, as well as to give some history as to // what jobs have been run previously type Jobs struct { running []*Job previous []*Job m sync.Mutex } func (jobs Jobs) MarshalJSON() ([]byte, error) { jobs.scan() return json.Marshal( struct { Running []*Job `json:"Running"` Previous []*Job `json:"Previous"` }{ Running: jobs.running, Previous: jobs.previous, }) } func (jobs Jobs) String() string { b, err := json.Marshal(jobs) if err != nil { panic(err) } return string(b) } func (jobs *Jobs) scan() { jobs.m.Lock() defer jobs.m.Unlock() debugLogJobs("scan on: '%s'\n", *jobs) running := []*Job{} notrunning := []*Job{} for _, job := range jobs.running { if job == nil { continue } if job.IsRunning() == true { running = append(running, job) } else { notrunning = append(notrunning, job) } } if len(notrunning) > 0 { jobs.previous = append(jobs.previous, notrunning...) } jobs.running = running } // Running returns true if any job is currently running, otherwise false func (jobs *Jobs) Running() bool { jobs.scan() jobs.m.Lock() defer jobs.m.Unlock() if len(jobs.running) == 0 { return false } debugLogJobs("running on: '%s'\n", *jobs) return true } // GetJobs returns all of the jobs regardless of their state func (jobs *Jobs) GetJobs() []*Job { jobs.scan() jobs.m.Lock() defer jobs.m.Unlock() debugLogJobs("GetJobs on: '%s'\n", *jobs) return append(jobs.running, jobs.previous...) } // GetRunningJobs returns all of the running jobs func (jobs *Jobs) GetRunningJobs() []*Job { jobs.scan() jobs.m.Lock() defer jobs.m.Unlock() debugLogJobs("GetRunningJobs on: '%s'\n", *jobs) return jobs.running } // GetStoppedJobs returns all of the stopped jobs func (jobs *Jobs) GetStoppedJobs() []*Job { jobs.scan() jobs.m.Lock() defer jobs.m.Unlock() debugLogJobs("GetStoppedJobs on: '%s'\n", *jobs) return jobs.previous } // addJob adds a job to the internal slice func (jobs *Jobs) addJob(j *Job) { jobs.m.Lock() jobs.running = append(jobs.running, j) debugLogJobs("addJob on: '%s'\n", *jobs) jobs.m.Unlock() jobs.scan() }