This commit is contained in:
Daniel
2024-11-27 16:16:15 +01:00
parent f91003d077
commit 706ce222d0
35 changed files with 1138 additions and 601 deletions

View File

@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"runtime"
"slices"
"strings"
"sync"
"time"
@@ -132,9 +133,11 @@ type Updater struct {
EventResourcesUpdated *mgr.EventMgr[struct{}]
corruptedInstallation bool
corruptedInstallation error
isUpdateRunning *abool.AtomicBool
started *abool.AtomicBool
configureLock sync.Mutex
instance instance
}
@@ -150,6 +153,7 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m),
isUpdateRunning: abool.NewBool(false),
started: abool.NewBool(false),
instance: instance,
}
@@ -166,6 +170,12 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
// Load index.
index, err := LoadIndex(filepath.Join(cfg.Directory, cfg.IndexFile), cfg.Verify)
if err == nil {
// Verify artifacts.
if err := index.VerifyArtifacts(cfg.Directory); err != nil {
module.corruptedInstallation = fmt.Errorf("invalid artifact: %w", err)
}
// Save index to module and return.
module.index = index
return module, nil
}
@@ -173,6 +183,7 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
// Fall back to scanning the directory.
if !errors.Is(err, os.ErrNotExist) {
log.Errorf("updates/%s: invalid index file, falling back to dir scan: %s", cfg.Name, err)
module.corruptedInstallation = fmt.Errorf("invalid index: %w", err)
}
index, err = GenerateIndexFromDir(cfg.Directory, IndexScanConfig{Version: "0.0.0"})
if err == nil && index.init() == nil {
@@ -259,6 +270,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
// Check if automatic downloads are enabled.
if !u.cfg.AutoDownload && !forceApply {
log.Infof("updates/%s: new update to v%s available, action required to download and upgrade", u.cfg.Name, downloader.index.Version)
if u.cfg.Notify && u.instance.Notifications() != nil {
u.instance.Notifications().Notify(&notifications.Notification{
EventID: updateAvailableNotificationID,
@@ -304,6 +316,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
// Notify the user that an upgrade is available.
if !u.cfg.AutoApply && !forceApply {
log.Infof("updates/%s: new update to v%s available, action required to upgrade", u.cfg.Name, downloader.index.Version)
if u.cfg.Notify && u.instance.Notifications() != nil {
u.instance.Notifications().Notify(&notifications.Notification{
EventID: updateAvailableNotificationID,
@@ -387,8 +400,15 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
return nil
}
func (u *Updater) getIndexURLsWithLock() []string {
u.configureLock.Lock()
defer u.configureLock.Unlock()
return u.cfg.IndexURLs
}
func (u *Updater) updateCheckWorker(w *mgr.WorkerCtx) error {
err := u.updateAndUpgrade(w, u.cfg.IndexURLs, false, false)
err := u.updateAndUpgrade(w, u.getIndexURLsWithLock(), false, false)
switch {
case err == nil:
return nil // Success!
@@ -404,7 +424,7 @@ func (u *Updater) updateCheckWorker(w *mgr.WorkerCtx) error {
}
func (u *Updater) upgradeWorker(w *mgr.WorkerCtx) error {
err := u.updateAndUpgrade(w, u.cfg.IndexURLs, false, true)
err := u.updateAndUpgrade(w, u.getIndexURLsWithLock(), false, true)
switch {
case err == nil:
return nil // Success!
@@ -423,7 +443,7 @@ func (u *Updater) upgradeWorker(w *mgr.WorkerCtx) error {
// and is intended to be used only within a tool, not a service.
func (u *Updater) ForceUpdate() error {
return u.m.Do("update and upgrade", func(w *mgr.WorkerCtx) error {
return u.updateAndUpgrade(w, u.cfg.IndexURLs, true, true)
return u.updateAndUpgrade(w, u.getIndexURLsWithLock(), true, true)
})
}
@@ -437,6 +457,33 @@ func (u *Updater) UpdateFromURL(url string) error {
return nil
}
// Configure makes slight configuration changes to the updater.
// It locks the index, which can take a while an update is running.
func (u *Updater) Configure(autoCheck bool, indexURLs []string) {
u.configureLock.Lock()
defer u.configureLock.Unlock()
// Apply new config.
var changed bool
if u.cfg.AutoCheck != autoCheck {
u.cfg.AutoCheck = autoCheck
changed = true
}
if !slices.Equal(u.cfg.IndexURLs, indexURLs) {
u.cfg.IndexURLs = indexURLs
changed = true
}
// Trigger update check if enabled and something changed.
if changed && u.started.IsSet() {
if autoCheck {
u.updateCheckWorkerMgr.Repeat(updateTaskRepeatDuration).Go()
} else {
u.updateCheckWorkerMgr.Repeat(0)
}
}
}
// TriggerUpdateCheck triggers an update check.
func (u *Updater) TriggerUpdateCheck() {
u.updateCheckWorkerMgr.Go()
@@ -459,13 +506,17 @@ func (u *Updater) Manager() *mgr.Manager {
// Start starts the module.
func (u *Updater) Start() error {
if u.corruptedInstallation && u.cfg.Notify && u.instance.Notifications() != nil {
// FIXME: this might make sense as a module state
u.instance.Notifications().NotifyError(
corruptInstallationNotificationID,
"Install Corruption",
"Portmaster has detected that one or more of its own files have been corrupted. Please re-install the software.",
)
u.configureLock.Lock()
defer u.configureLock.Unlock()
if u.corruptedInstallation != nil && u.cfg.Notify && u.instance.Notifications() != nil {
u.states.Add(mgr.State{
ID: corruptInstallationNotificationID,
Name: "Install Corruption",
Message: "Portmaster has detected that one or more of its own files have been corrupted. Please re-install the software. Error: " + u.corruptedInstallation.Error(),
Type: mgr.StateTypeError,
Data: u.corruptedInstallation,
})
}
// Check for updates automatically, if enabled.
@@ -474,6 +525,8 @@ func (u *Updater) Start() error {
Repeat(updateTaskRepeatDuration).
Delay(15 * time.Second)
}
u.started.SetTo(true)
return nil
}
@@ -481,6 +534,62 @@ func (u *Updater) GetMainDir() string {
return u.cfg.Directory
}
// GetIndex returns a copy of the index.
func (u *Updater) GetIndex() (*Index, error) {
// Copy Artifacts.
artifacts, err := u.GetFiles()
if err != nil {
return nil, err
}
u.indexLock.Lock()
defer u.indexLock.Unlock()
// Check if any index is active.
if u.index == nil {
return nil, ErrNotFound
}
return &Index{
Name: u.index.Name,
Version: u.index.Version,
Published: u.index.Published,
Artifacts: artifacts,
versionNum: u.index.versionNum,
}, nil
}
// GetFiles returns all artifacts. Returns ErrNotFound if no artifacts are found.
func (u *Updater) GetFiles() ([]*Artifact, error) {
u.indexLock.Lock()
defer u.indexLock.Unlock()
// Check if any index is active.
if u.index == nil {
return nil, ErrNotFound
}
// Export all artifacts.
export := make([]*Artifact, 0, len(u.index.Artifacts))
for _, artifact := range u.index.Artifacts {
switch {
case artifact.Platform != "" && artifact.Platform != currentPlatform:
// Platform is defined and does not match.
// Platforms are usually pre-filtered, but just to be sure.
default:
// Artifact matches!
export = append(export, artifact.export(u.cfg.Directory, u.index.versionNum))
}
}
// Check if anything was exported.
if len(export) == 0 {
return nil, ErrNotFound
}
return export, nil
}
// GetFile returns the path of a file given the name. Returns ErrNotFound if file is not found.
func (u *Updater) GetFile(name string) (*Artifact, error) {
u.indexLock.Lock()
@@ -509,6 +618,7 @@ func (u *Updater) GetFile(name string) (*Artifact, error) {
// Stop stops the module.
func (u *Updater) Stop() error {
u.started.SetTo(false)
return nil
}