package updates import ( "context" "fmt" "sort" "strings" "sync" "github.com/safing/portbase/database/record" "github.com/safing/portbase/info" "github.com/safing/portbase/log" "github.com/safing/portbase/updater" "github.com/safing/portbase/utils/debug" "github.com/safing/portmaster/service/updates/helper" ) const ( // versionsDBKey is the database key for update version information. versionsDBKey = "core:status/versions" // versionsDBKey is the database key for simple update version information. simpleVersionsDBKey = "core:status/simple-versions" // updateStatusDBKey is the database key for update status information. updateStatusDBKey = "core:status/updates" ) // Versions holds update versions and status information. type Versions struct { record.Base sync.Mutex Core *info.Info Resources map[string]*updater.Resource Channel string Beta bool Staging bool } // SimpleVersions holds simplified update versions and status information. type SimpleVersions struct { record.Base sync.Mutex Build *info.Info Resources map[string]*SimplifiedResourceVersion Channel string } // SimplifiedResourceVersion holds version information about one resource. type SimplifiedResourceVersion struct { Version string } // UpdateStateExport is a wrapper to export the updates state. type UpdateStateExport struct { record.Base sync.Mutex *updater.UpdateState } // GetVersions returns the update versions and status information. // Resources must be locked when accessed. func GetVersions() *Versions { return &Versions{ Core: info.GetInfo(), Resources: registry.Export(), Channel: initialReleaseChannel, Beta: initialReleaseChannel == helper.ReleaseChannelBeta, Staging: initialReleaseChannel == helper.ReleaseChannelStaging, } } // GetSimpleVersions returns the simplified update versions and status information. func GetSimpleVersions() *SimpleVersions { // Fill base info. v := &SimpleVersions{ Build: info.GetInfo(), Resources: make(map[string]*SimplifiedResourceVersion), Channel: initialReleaseChannel, } // Iterate through all versions and add version info. for id, resource := range registry.Export() { func() { resource.Lock() defer resource.Unlock() // Get current in-used or selected version. var rv *updater.ResourceVersion switch { case resource.ActiveVersion != nil: rv = resource.ActiveVersion case resource.SelectedVersion != nil: rv = resource.SelectedVersion } // Get information from resource. if rv != nil { v.Resources[id] = &SimplifiedResourceVersion{ Version: rv.VersionNumber, } } }() } return v } // GetStateExport gets the update state from the registry and returns it in an // exportable struct. func GetStateExport() *UpdateStateExport { export := registry.GetState() return &UpdateStateExport{ UpdateState: &export.Updates, } } // LoadStateExport loads the exported update state from the database. func LoadStateExport() (*UpdateStateExport, error) { r, err := db.Get(updateStatusDBKey) if err != nil { return nil, err } // unwrap if r.IsWrapped() { // only allocate a new struct, if we need it newRecord := &UpdateStateExport{} err = record.Unwrap(r, newRecord) if err != nil { return nil, err } return newRecord, nil } // or adjust type newRecord, ok := r.(*UpdateStateExport) if !ok { return nil, fmt.Errorf("record not of type *UpdateStateExport, but %T", r) } return newRecord, nil } func initVersionExport() (err error) { if err := GetVersions().save(); err != nil { log.Warningf("updates: failed to export version information: %s", err) } if err := GetSimpleVersions().save(); err != nil { log.Warningf("updates: failed to export version information: %s", err) } return module.RegisterEventHook( ModuleName, VersionUpdateEvent, "export version status", export, ) } func (v *Versions) save() error { if !v.KeyIsSet() { v.SetKey(versionsDBKey) } return db.Put(v) } func (v *SimpleVersions) save() error { if !v.KeyIsSet() { v.SetKey(simpleVersionsDBKey) } return db.Put(v) } func (s *UpdateStateExport) save() error { if !s.KeyIsSet() { s.SetKey(updateStatusDBKey) } return db.Put(s) } // export is an event hook. func export(_ context.Context, _ interface{}) error { // Export versions. if err := GetVersions().save(); err != nil { return err } if err := GetSimpleVersions().save(); err != nil { return err } // Export udpate state. if err := GetStateExport().save(); err != nil { return err } return nil } // AddToDebugInfo adds the update system status to the given debug.Info. func AddToDebugInfo(di *debug.Info) { // Get resources from registry. resources := registry.Export() platformPrefix := helper.PlatformIdentifier("") // Collect data for debug info. var active, selected []string var activeCnt, totalCnt int for id, r := range resources { // Ignore resources for other platforms. if !strings.HasPrefix(id, "all/") && !strings.HasPrefix(id, platformPrefix) { continue } totalCnt++ if r.ActiveVersion != nil { activeCnt++ active = append(active, fmt.Sprintf("%s: %s", id, r.ActiveVersion.VersionNumber)) } if r.SelectedVersion != nil { selected = append(selected, fmt.Sprintf("%s: %s", id, r.SelectedVersion.VersionNumber)) } } sort.Strings(active) sort.Strings(selected) // Compile to one list. lines := make([]string, 0, len(active)+len(selected)+3) lines = append(lines, "Active:") lines = append(lines, active...) lines = append(lines, "") lines = append(lines, "Selected:") lines = append(lines, selected...) // Add section. di.AddSection( fmt.Sprintf("Updates: %s (%d/%d)", initialReleaseChannel, activeCnt, totalCnt), debug.UseCodeSection|debug.AddContentLineBreaks, lines..., ) }