mirror of
https://github.com/safing/portbase
synced 2025-09-02 10:40:39 +00:00
Merge pull request #38 from safing/fix/updates
Fix update not marked as available, add comments and notification helper
This commit is contained in:
commit
0a53e78e8a
4 changed files with 142 additions and 18 deletions
|
@ -67,6 +67,48 @@ func Get(id string) *Notification {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyInfo is a helper method for quickly showing a info
|
||||||
|
// notification. The notification is already shown. If id is
|
||||||
|
// an empty string a new UUIDv4 will be generated.
|
||||||
|
func NotifyInfo(id, msg string, actions ...Action) *Notification {
|
||||||
|
return notify(Info, id, msg, actions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyWarn is a helper method for quickly showing a warning
|
||||||
|
// notification. The notification is already shown. If id is
|
||||||
|
// an empty string a new UUIDv4 will be generated.
|
||||||
|
func NotifyWarn(id, msg string, actions ...Action) *Notification {
|
||||||
|
return notify(Warning, id, msg, actions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyPrompt is a helper method for quickly showing a prompt
|
||||||
|
// notification. The notification is already shown. If id is
|
||||||
|
// an empty string a new UUIDv4 will be generated.
|
||||||
|
func NotifyPrompt(id, msg string, actions ...Action) *Notification {
|
||||||
|
return notify(Prompt, id, msg, actions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notify(nType uint8, id string, msg string, actions ...Action) *Notification {
|
||||||
|
acts := make([]*Action, len(actions))
|
||||||
|
for idx := range actions {
|
||||||
|
a := actions[idx]
|
||||||
|
acts[idx] = &a
|
||||||
|
}
|
||||||
|
|
||||||
|
if id == "" {
|
||||||
|
id = uuid.NewV4().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
n := Notification{
|
||||||
|
ID: id,
|
||||||
|
Message: msg,
|
||||||
|
Type: nType,
|
||||||
|
AvailableActions: acts,
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.Save()
|
||||||
|
}
|
||||||
|
|
||||||
// Save saves the notification and returns it.
|
// Save saves the notification and returns it.
|
||||||
func (n *Notification) Save() *Notification {
|
func (n *Notification) Save() *Notification {
|
||||||
notsLock.Lock()
|
notsLock.Lock()
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package updater
|
package updater
|
||||||
|
|
||||||
|
import "github.com/safing/portbase/log"
|
||||||
|
|
||||||
// File represents a file from the update system.
|
// File represents a file from the update system.
|
||||||
type File struct {
|
type File struct {
|
||||||
resource *Resource
|
resource *Resource
|
||||||
|
@ -36,6 +38,7 @@ func (file *File) markActiveWithLocking() {
|
||||||
|
|
||||||
// update last used version
|
// update last used version
|
||||||
if file.resource.ActiveVersion != file.version {
|
if file.resource.ActiveVersion != file.version {
|
||||||
|
log.Debugf("updater: setting active version of resource %s from %s to %s", file.resource.Identifier, file.resource.ActiveVersion, file.version.VersionNumber)
|
||||||
file.resource.ActiveVersion = file.version
|
file.resource.ActiveVersion = file.version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,37 +19,90 @@ type Resource struct {
|
||||||
registry *ResourceRegistry
|
registry *ResourceRegistry
|
||||||
notifier *notifier
|
notifier *notifier
|
||||||
|
|
||||||
|
// Identifier is the unique identifier for that resource.
|
||||||
|
// It forms a file path using a forward-slash as the
|
||||||
|
// path separator.
|
||||||
Identifier string
|
Identifier string
|
||||||
|
|
||||||
|
// Versions holds all available resource versions.
|
||||||
Versions []*ResourceVersion
|
Versions []*ResourceVersion
|
||||||
|
|
||||||
|
// ActiveVersion is the last version of the resource
|
||||||
|
// that someone requested using GetFile().
|
||||||
ActiveVersion *ResourceVersion
|
ActiveVersion *ResourceVersion
|
||||||
|
|
||||||
|
// SelectedVersion is newest, selectable version of
|
||||||
|
// that resource that is available. A version
|
||||||
|
// is selectable if it's not blacklisted by the user.
|
||||||
|
// Note that it's not guaranteed that the selected version
|
||||||
|
// is available locally. In that case, GetFile will attempt
|
||||||
|
// to download the latest version from the updates servers
|
||||||
|
// specified in the resource registry.
|
||||||
SelectedVersion *ResourceVersion
|
SelectedVersion *ResourceVersion
|
||||||
ForceDownload bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceVersion represents a single version of a resource.
|
// ResourceVersion represents a single version of a resource.
|
||||||
type ResourceVersion struct {
|
type ResourceVersion struct {
|
||||||
resource *Resource
|
resource *Resource
|
||||||
|
|
||||||
|
// VersionNumber is the string representation of the resource
|
||||||
|
// version.
|
||||||
VersionNumber string
|
VersionNumber string
|
||||||
semVer *semver.Version
|
semVer *semver.Version
|
||||||
|
|
||||||
|
// Available indicates if this version is available locally.
|
||||||
Available bool
|
Available bool
|
||||||
|
|
||||||
|
// StableRelease indicates that this version is part of
|
||||||
|
// a stable release index file.
|
||||||
StableRelease bool
|
StableRelease bool
|
||||||
|
|
||||||
|
// BetaRelease indicates that this version is part of
|
||||||
|
// a beta release index file.
|
||||||
BetaRelease bool
|
BetaRelease bool
|
||||||
|
|
||||||
|
// Blacklisted may be set to true if this version should
|
||||||
|
// be skipped and not used. This is useful if the version
|
||||||
|
// is known to be broken.
|
||||||
Blacklisted bool
|
Blacklisted bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len is the number of elements in the collection. (sort.Interface for Versions)
|
func (rv *ResourceVersion) String() string {
|
||||||
|
return rv.VersionNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSelectable returns true if the version represented by rv is selectable.
|
||||||
|
// A version is selectable if it's not blacklisted and either already locally
|
||||||
|
// available or ready to be downloaded.
|
||||||
|
func (rv *ResourceVersion) isSelectable() bool {
|
||||||
|
return !rv.Blacklisted && (rv.Available || rv.resource.registry.Online)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isBetaVersionNumber checks if rv is marked as a beta version by checking
|
||||||
|
// the version string. It does not honor the BetaRelease field of rv!
|
||||||
|
func (rv *ResourceVersion) isBetaVersionNumber() bool {
|
||||||
|
// "b" suffix check if for backwards compatibility
|
||||||
|
// new versions should use the pre-release suffix as
|
||||||
|
// declared by https://semver.org
|
||||||
|
// i.e. 1.2.3-beta
|
||||||
|
return strings.HasSuffix(rv.VersionNumber, "b") || strings.Contains(rv.semVer.Prerelease(), "beta")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len is the number of elements in the collection.
|
||||||
|
// It implements sort.Interface for ResourceVersion.
|
||||||
func (res *Resource) Len() int {
|
func (res *Resource) Len() int {
|
||||||
return len(res.Versions)
|
return len(res.Versions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Less reports whether the element with index i should sort before the element with index j. (sort.Interface for Versions)
|
// Less reports whether the element with index i should
|
||||||
|
// sort before the element with index j.
|
||||||
|
// It implements sort.Interface for ResourceVersions.
|
||||||
func (res *Resource) Less(i, j int) bool {
|
func (res *Resource) Less(i, j int) bool {
|
||||||
return res.Versions[i].semVer.GreaterThan(res.Versions[j].semVer)
|
return res.Versions[i].semVer.GreaterThan(res.Versions[j].semVer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swap swaps the elements with indexes i and j. (sort.Interface for Versions)
|
// Swap swaps the elements with indexes i and j.
|
||||||
|
// It implements sort.Interface for ResourceVersions.
|
||||||
func (res *Resource) Swap(i, j int) {
|
func (res *Resource) Swap(i, j int) {
|
||||||
res.Versions[i], res.Versions[j] = res.Versions[j], res.Versions[i]
|
res.Versions[i], res.Versions[j] = res.Versions[j], res.Versions[i]
|
||||||
}
|
}
|
||||||
|
@ -64,6 +117,20 @@ func (res *Resource) available() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inUse returns true if the resource is currently in use.
|
||||||
|
func (res *Resource) inUse() bool {
|
||||||
|
return res.ActiveVersion != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyVersionAvailable returns true if any version of
|
||||||
|
// res is locally available.
|
||||||
|
func (res *Resource) AnyVersionAvailable() bool {
|
||||||
|
res.Lock()
|
||||||
|
defer res.Unlock()
|
||||||
|
|
||||||
|
return res.available()
|
||||||
|
}
|
||||||
|
|
||||||
func (reg *ResourceRegistry) newResource(identifier string) *Resource {
|
func (reg *ResourceRegistry) newResource(identifier string) *Resource {
|
||||||
return &Resource{
|
return &Resource{
|
||||||
registry: reg,
|
registry: reg,
|
||||||
|
@ -154,18 +221,24 @@ func (res *Resource) GetFile() *File {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocognit // function already kept as simlpe as possible
|
//nolint:gocognit // function already kept as simple as possible
|
||||||
func (res *Resource) selectVersion() {
|
func (res *Resource) selectVersion() {
|
||||||
sort.Sort(res)
|
sort.Sort(res)
|
||||||
|
|
||||||
// export after we finish
|
// export after we finish
|
||||||
defer func() {
|
defer func() {
|
||||||
if res.ActiveVersion != nil && // resource has already been used
|
log.Debugf("updater: selected version %s for resource %s", res.SelectedVersion, res.Identifier)
|
||||||
|
|
||||||
|
if res.inUse() &&
|
||||||
res.SelectedVersion != res.ActiveVersion && // new selected version does not match previously selected version
|
res.SelectedVersion != res.ActiveVersion && // new selected version does not match previously selected version
|
||||||
res.notifier != nil {
|
res.notifier != nil {
|
||||||
|
|
||||||
res.notifier.markAsUpgradeable()
|
res.notifier.markAsUpgradeable()
|
||||||
res.notifier = nil
|
res.notifier = nil
|
||||||
|
|
||||||
|
log.Debugf("updater: active version of %s is %s, update available", res.Identifier, res.ActiveVersion.VersionNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if len(res.Versions) == 0 {
|
if len(res.Versions) == 0 {
|
||||||
|
@ -190,7 +263,7 @@ func (res *Resource) selectVersion() {
|
||||||
if res.registry.Beta {
|
if res.registry.Beta {
|
||||||
for _, rv := range res.Versions {
|
for _, rv := range res.Versions {
|
||||||
if rv.BetaRelease {
|
if rv.BetaRelease {
|
||||||
if !rv.Blacklisted && (rv.Available || rv.resource.registry.Online) {
|
if rv.isSelectable() {
|
||||||
res.SelectedVersion = rv
|
res.SelectedVersion = rv
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -202,7 +275,7 @@ func (res *Resource) selectVersion() {
|
||||||
// 3) Stable release
|
// 3) Stable release
|
||||||
for _, rv := range res.Versions {
|
for _, rv := range res.Versions {
|
||||||
if rv.StableRelease {
|
if rv.StableRelease {
|
||||||
if !rv.Blacklisted && (rv.Available || rv.resource.registry.Online) {
|
if rv.isSelectable() {
|
||||||
res.SelectedVersion = rv
|
res.SelectedVersion = rv
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -212,7 +285,7 @@ func (res *Resource) selectVersion() {
|
||||||
|
|
||||||
// 4) Latest stable release
|
// 4) Latest stable release
|
||||||
for _, rv := range res.Versions {
|
for _, rv := range res.Versions {
|
||||||
if !strings.HasSuffix(rv.VersionNumber, "b") && !rv.Blacklisted && (rv.Available || rv.resource.registry.Online) {
|
if !rv.isBetaVersionNumber() && rv.isSelectable() {
|
||||||
res.SelectedVersion = rv
|
res.SelectedVersion = rv
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -220,7 +293,7 @@ func (res *Resource) selectVersion() {
|
||||||
|
|
||||||
// 5) Latest of any type
|
// 5) Latest of any type
|
||||||
for _, rv := range res.Versions {
|
for _, rv := range res.Versions {
|
||||||
if !rv.Blacklisted && (rv.Available || rv.resource.registry.Online) {
|
if rv.isSelectable() {
|
||||||
res.SelectedVersion = rv
|
res.SelectedVersion = rv
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -228,6 +301,7 @@ func (res *Resource) selectVersion() {
|
||||||
|
|
||||||
// 6) Default to newest
|
// 6) Default to newest
|
||||||
res.SelectedVersion = res.Versions[0]
|
res.SelectedVersion = res.Versions[0]
|
||||||
|
log.Warningf("updater: falling back to version %s for %s because we failed to find a selectable one", res.SelectedVersion, res.Identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blacklist blacklists the specified version and selects a new version.
|
// Blacklist blacklists the specified version and selects a new version.
|
||||||
|
@ -235,7 +309,7 @@ func (res *Resource) Blacklist(version string) error {
|
||||||
res.Lock()
|
res.Lock()
|
||||||
defer res.Unlock()
|
defer res.Unlock()
|
||||||
|
|
||||||
// count already blacklisted entries
|
// count available and valid versions
|
||||||
valid := 0
|
valid := 0
|
||||||
for _, rv := range res.Versions {
|
for _, rv := range res.Versions {
|
||||||
if rv.VersionNumber == "0" {
|
if rv.VersionNumber == "0" {
|
||||||
|
@ -262,7 +336,11 @@ func (res *Resource) Blacklist(version string) error {
|
||||||
return errors.New("could not find version")
|
return errors.New("could not find version")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge deletes old updates, retaining a certain amount, specified by the keep parameter. Will at least keep 2 updates per resource. After purging, new versions will be selected.
|
// Purge deletes old updates, retaining a certain amount, specified by
|
||||||
|
// the keep parameter. Purge will always keep at least 2 versions so
|
||||||
|
// specifying a smaller keep value will have no effect. Note that
|
||||||
|
// blacklisted versions are not counted for the keep parameter.
|
||||||
|
// After purging a new version will be selected.
|
||||||
func (res *Resource) Purge(keep int) {
|
func (res *Resource) Purge(keep int) {
|
||||||
res.Lock()
|
res.Lock()
|
||||||
defer res.Unlock()
|
defer res.Unlock()
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (reg *ResourceRegistry) DownloadUpdates(ctx context.Context) error {
|
||||||
res.Lock()
|
res.Lock()
|
||||||
|
|
||||||
// check if we want to download
|
// check if we want to download
|
||||||
if res.ActiveVersion != nil || // resource is currently being used
|
if res.inUse() ||
|
||||||
res.available() || // resource was used in the past
|
res.available() || // resource was used in the past
|
||||||
utils.StringInSlice(reg.MandatoryUpdates, res.Identifier) { // resource is mandatory
|
utils.StringInSlice(reg.MandatoryUpdates, res.Identifier) { // resource is mandatory
|
||||||
|
|
||||||
|
@ -111,6 +111,7 @@ func (reg *ResourceRegistry) DownloadUpdates(ctx context.Context) error {
|
||||||
for tries := 0; tries < 3; tries++ {
|
for tries := 0; tries < 3; tries++ {
|
||||||
err = reg.fetchFile(rv, tries)
|
err = reg.fetchFile(rv, tries)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
rv.Available = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue