WIP
This commit is contained in:
@@ -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(¬ifications.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(¬ifications.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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user