mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-20 02:04:01 -04:00
scrollbar: interface style toggle and no scrollbars in the dash dash: never allow scrollbars cc: widget sheet cards, header close button and content-sized dialogs
152 lines
3.8 KiB
Go
152 lines
3.8 KiB
Go
package sysupdate
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/netfetch"
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/site"
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
|
)
|
|
|
|
const (
|
|
releasesURL = site.API + "/dms/releases"
|
|
releasesTimeout = 10 * time.Second
|
|
releasesMinAge = time.Hour
|
|
releasesMaxBytes = 1 << 20
|
|
)
|
|
|
|
var releasesFetch = func(ctx context.Context, etag string) (netfetch.Conditional, error) {
|
|
return netfetch.BytesConditional(ctx, releasesURL, etag, netfetch.Options{Timeout: releasesTimeout, MaxBytes: releasesMaxBytes})
|
|
}
|
|
|
|
func cacheDir() string {
|
|
return filepath.Join(utils.XDGCacheHome(), "dms")
|
|
}
|
|
|
|
func releasesCachePath() string {
|
|
return filepath.Join(cacheDir(), "releases.json")
|
|
}
|
|
|
|
// The feed never lives in Manager memory; every reader goes through the cache file.
|
|
func LoadReleases() (ReleasesFeed, bool) {
|
|
data, err := os.ReadFile(releasesCachePath())
|
|
if err != nil {
|
|
return ReleasesFeed{}, false
|
|
}
|
|
var feed ReleasesFeed
|
|
if err := json.Unmarshal(data, &feed); err != nil {
|
|
return ReleasesFeed{}, false
|
|
}
|
|
if feed.Releases == nil {
|
|
feed.Releases = []Release{}
|
|
}
|
|
return feed, true
|
|
}
|
|
|
|
func saveReleases(feed ReleasesFeed) error {
|
|
data, err := json.Marshal(feed)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeFileAtomic(releasesCachePath(), data)
|
|
}
|
|
|
|
func writeFileAtomic(path string, data []byte) error {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, data, 0o644); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(tmp, path)
|
|
}
|
|
|
|
// Serialized so overlapping requests don't fetch twice.
|
|
func (m *Manager) refreshReleases(ctx context.Context, force bool) (ReleasesFeed, error) {
|
|
m.releasesMu.Lock()
|
|
defer m.releasesMu.Unlock()
|
|
cached, ok := LoadReleases()
|
|
if ok && !force && time.Since(time.Unix(cached.FetchedAt, 0)) < releasesMinAge {
|
|
return cached, nil
|
|
}
|
|
|
|
res, err := releasesFetch(ctx, cached.ETag)
|
|
if err != nil {
|
|
if ok {
|
|
return cached, err
|
|
}
|
|
return ReleasesFeed{Releases: []Release{}}, err
|
|
}
|
|
|
|
now := time.Now().Unix()
|
|
feed := cached
|
|
if !res.NotModified {
|
|
feed = ReleasesFeed{}
|
|
if err := json.Unmarshal(res.Body, &feed); err != nil {
|
|
if ok {
|
|
return cached, err
|
|
}
|
|
return ReleasesFeed{Releases: []Release{}}, err
|
|
}
|
|
if feed.Releases == nil {
|
|
feed.Releases = []Release{}
|
|
}
|
|
}
|
|
feed.FetchedAt = now
|
|
feed.ETag = res.ETag
|
|
if err := saveReleases(feed); err != nil {
|
|
log.Warnf("[sysupdate] save releases cache: %v", err)
|
|
}
|
|
m.applyReleases(feed)
|
|
return feed, nil
|
|
}
|
|
|
|
func (m *Manager) applyReleases(feed ReleasesFeed) {
|
|
m.mu.Lock()
|
|
m.state.Shell.CommitsBehind = commitsBehind(m.state.Shell, feed.Master)
|
|
m.mu.Unlock()
|
|
m.markDirty()
|
|
}
|
|
|
|
// Packages ride along so a restart doesn't show an empty list until the next daily check.
|
|
type persistedState struct {
|
|
LastCheckUnix int64 `json:"lastCheckUnix"`
|
|
LastSuccessUnix int64 `json:"lastSuccessUnix"`
|
|
Packages []Package `json:"packages,omitempty"`
|
|
// Survives shell restarts, not the reboot itself.
|
|
RebootBootID string `json:"rebootBootId,omitempty"`
|
|
RebootPackages []string `json:"rebootPackages,omitempty"`
|
|
}
|
|
|
|
func statePath() string {
|
|
return filepath.Join(cacheDir(), "sysupdate.json")
|
|
}
|
|
|
|
func loadPersisted() persistedState {
|
|
var s persistedState
|
|
data, err := os.ReadFile(statePath())
|
|
if err != nil {
|
|
return s
|
|
}
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return persistedState{}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func savePersisted(s persistedState) {
|
|
data, err := json.Marshal(s)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if err := writeFileAtomic(statePath(), data); err != nil {
|
|
log.Debugf("[sysupdate] persist state: %v", err)
|
|
}
|
|
}
|