safing-portbase/updater/state.go
2023-04-06 14:34:27 +02:00

180 lines
4.4 KiB
Go

package updater
import (
"sort"
"sync"
"time"
"github.com/safing/portbase/utils"
)
// Registry States.
const (
StateReady = "ready" // Default idle state.
StateChecking = "checking" // Downloading indexes.
StateDownloading = "downloading" // Downloading updates.
StateFetching = "fetching" // Fetching a single file.
)
// RegistryState describes the registry state.
type RegistryState struct {
sync.Mutex
reg *ResourceRegistry
// ID holds the ID of the state the registry is currently in.
ID string
// Details holds further information about the current state.
Details any
// Updates holds generic information about the current status of pending
// and recently downloaded updates.
Updates UpdateState
// operationLock locks the operation of any state changing operation.
// This is separate from the registry lock, which locks access to the
// registry struct.
operationLock sync.Mutex
}
// StateDownloadingDetails holds details of the downloading state.
type StateDownloadingDetails struct {
// Resources holds the resource IDs that are being downloaded.
Resources []string
// FinishedUpTo holds the index of Resources that is currently being
// downloaded. Previous resources have finished downloading.
FinishedUpTo int
}
// UpdateState holds generic information about the current status of pending
// and recently downloaded updates.
type UpdateState struct {
// LastCheckAt holds the time of the last update check.
LastCheckAt *time.Time
// LastCheckError holds the error of the last check.
LastCheckError error
// PendingDownload holds the resources that are pending download.
PendingDownload []string
// LastDownloadAt holds the time when resources were downloaded the last time.
LastDownloadAt *time.Time
// LastDownloadError holds the error of the last download.
LastDownloadError error
// LastDownload holds the resources that we downloaded the last time updates
// were downloaded.
LastDownload []string
// LastSuccessAt holds the time of the last successful update (check).
LastSuccessAt *time.Time
}
// GetState returns the current registry state.
// The returned data must not be modified.
func (reg *ResourceRegistry) GetState() RegistryState {
reg.state.Lock()
defer reg.state.Unlock()
return RegistryState{
ID: reg.state.ID,
Details: reg.state.Details,
Updates: reg.state.Updates,
}
}
// StartOperation starts an operation.
func (s *RegistryState) StartOperation(id string) bool {
defer s.notify()
s.operationLock.Lock()
s.Lock()
defer s.Unlock()
s.ID = id
return true
}
// UpdateOperationDetails updates the details of an operation.
// The supplied struct should be a copy and must not be changed after calling
// this function.
func (s *RegistryState) UpdateOperationDetails(details any) {
defer s.notify()
s.Lock()
defer s.Unlock()
s.Details = details
}
// EndOperation ends an operation.
func (s *RegistryState) EndOperation() {
defer s.notify()
defer s.operationLock.Unlock()
s.Lock()
defer s.Unlock()
s.ID = StateReady
s.Details = nil
}
// ReportUpdateCheck reports an update check to the registry state.
func (s *RegistryState) ReportUpdateCheck(pendingDownload []string, failed error) {
defer s.notify()
sort.Strings(pendingDownload)
s.Lock()
defer s.Unlock()
now := time.Now()
s.Updates.LastCheckAt = &now
s.Updates.LastCheckError = failed
s.Updates.PendingDownload = pendingDownload
if failed == nil {
s.Updates.LastSuccessAt = &now
}
}
// ReportDownloads reports downloaded updates to the registry state.
func (s *RegistryState) ReportDownloads(downloaded []string, failed error) {
defer s.notify()
sort.Strings(downloaded)
s.Lock()
defer s.Unlock()
now := time.Now()
s.Updates.LastDownloadAt = &now
s.Updates.LastDownloadError = failed
s.Updates.LastDownload = downloaded
// Remove downloaded resources from the pending list.
if len(s.Updates.PendingDownload) > 0 {
newPendingDownload := make([]string, 0, len(s.Updates.PendingDownload))
for _, pending := range s.Updates.PendingDownload {
if !utils.StringInSlice(downloaded, pending) {
newPendingDownload = append(newPendingDownload, pending)
}
}
s.Updates.PendingDownload = newPendingDownload
}
if failed == nil {
s.Updates.LastSuccessAt = &now
}
}
func (s *RegistryState) notify() {
switch {
case s.reg == nil:
return
case s.reg.StateNotifyFunc == nil:
return
}
s.reg.StateNotifyFunc(s)
}