From cca12d7327bcb11e38f894fcfbcfbdcc421ec96c Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 29 Mar 2023 18:14:46 -0700 Subject: [PATCH] create UpdateChecker background task + constants refactoring --- backend/app.go | 51 ++++++++++++++++------------- backend/config.go | 16 +++++---- backend/servermanager.go | 11 ++++--- backend/updatechecker.go | 71 ++++++++++++++++++++++++++++++++++++++++ backend/util/util.go | 18 ---------- main.go | 12 ++++--- 6 files changed, 121 insertions(+), 58 deletions(-) create mode 100644 backend/updatechecker.go diff --git a/backend/app.go b/backend/app.go index f88f9f9..d1cddea 100644 --- a/backend/app.go +++ b/backend/app.go @@ -10,15 +10,14 @@ import ( "supersonic/backend/util" "supersonic/player" "supersonic/sharedutil" + "time" "github.com/20after4/configdir" "github.com/zalando/go-keyring" ) const ( - AppName = "supersonic" - configFile = "config.toml" - LatestReleaseURL = "https://github.com/dweymouth/supersonic/releases/latest" + configFile = "config.toml" ) var ( @@ -32,21 +31,27 @@ type App struct { LibraryManager *LibraryManager PlaybackManager *PlaybackManager Player *player.Player + UpdateChecker UpdateChecker - bgrndCtx context.Context - cancel context.CancelFunc + appName string + appVersionTag string + bgrndCtx context.Context + cancel context.CancelFunc } -func StartupApp() (*App, error) { - a := &App{} +func StartupApp(appName, appVersionTag, latestReleaseURL string) (*App, error) { + a := &App{appName: appName, appVersionTag: appVersionTag} a.bgrndCtx, a.cancel = context.WithCancel(context.Background()) - log.Printf("Starting %s...", AppName) - log.Printf("Using config dir: %s", configdir.LocalConfig(AppName)) - log.Printf("Using cache dir: %s", configdir.LocalCache(AppName)) + log.Printf("Starting %s...", appName) + log.Printf("Using config dir: %s", configdir.LocalConfig(appName)) + log.Printf("Using cache dir: %s", configdir.LocalCache(appName)) a.readConfig() + a.UpdateChecker = NewUpdateChecker(appVersionTag, latestReleaseURL, &a.Config.Application.LastCheckedVersion) + a.UpdateChecker.Start(a.bgrndCtx, 24*time.Hour) + if err := a.initMPV(); err != nil { return nil, err } @@ -63,10 +68,10 @@ func StartupApp() (*App, error) { PreampGain: a.Config.ReplayGain.PreampGainDB, }) - a.ServerManager = NewServerManager() + a.ServerManager = NewServerManager(appName) a.PlaybackManager = NewPlaybackManager(a.bgrndCtx, a.ServerManager, a.Player, &a.Config.Scrobbling) a.LibraryManager = NewLibraryManager(a.ServerManager) - a.ImageManager = NewImageManager(a.bgrndCtx, a.ServerManager, configdir.LocalCache(AppName)) + a.ImageManager = NewImageManager(a.bgrndCtx, a.ServerManager, configdir.LocalCache(a.appName)) a.LibraryManager.PreCacheCoverFn = func(coverID string) { _, _ = a.ImageManager.GetAlbumThumbnail(coverID) } @@ -75,23 +80,23 @@ func StartupApp() (*App, error) { } func (a *App) readConfig() { - configdir.MakePath(configdir.LocalConfig(AppName)) - cfgPath := configPath() - cfg, err := ReadConfigFile(cfgPath) + configdir.MakePath(configdir.LocalConfig(a.appName)) + cfgPath := a.configPath() + cfg, err := ReadConfigFile(cfgPath, a.appVersionTag) if err != nil { log.Printf("Error reading app config file: %v", err) - cfg = DefaultConfig() + cfg = DefaultConfig(a.appVersionTag) if _, err := os.Stat(cfgPath); err == nil { backupCfgName := fmt.Sprintf("%s.bak", configFile) log.Printf("Config file may be malformed: copying to %s", backupCfgName) - _ = util.CopyFile(cfgPath, path.Join(configdir.LocalConfig(AppName), backupCfgName)) + _ = util.CopyFile(cfgPath, path.Join(configdir.LocalConfig(a.appName), backupCfgName)) } } a.Config = cfg } func (a *App) initMPV() error { - p := player.NewWithClientName(AppName) + p := player.NewWithClientName(a.appName) c := a.Config.LocalPlayback c.InMemoryCacheSizeMB = clamp(c.InMemoryCacheSizeMB, 10, 500) if err := p.Init(c.AudioExclusive, c.InMemoryCacheSizeMB); err != nil { @@ -101,12 +106,12 @@ func (a *App) initMPV() error { return nil } -func (a *App) LoginToDefaultServer() error { +func (a *App) LoginToDefaultServer(string) error { serverCfg := a.Config.GetDefaultServer() if serverCfg == nil { return ErrNoServers } - pass, err := keyring.Get(AppName, serverCfg.ID.String()) + pass, err := keyring.Get(a.appName, serverCfg.ID.String()) if err != nil { return fmt.Errorf("error reading keyring credentials: %v", err) } @@ -118,11 +123,11 @@ func (a *App) Shutdown() { a.Config.LocalPlayback.Volume = a.Player.GetVolume() a.cancel() a.Player.Destroy() - a.Config.WriteConfigFile(configPath()) + a.Config.WriteConfigFile(a.configPath()) } -func configPath() string { - return path.Join(configdir.LocalConfig(AppName), configFile) +func (a *App) configPath() string { + return path.Join(configdir.LocalConfig(a.appName), configFile) } func clamp(i, min, max int) int { diff --git a/backend/config.go b/backend/config.go index da2455f..c9a29ea 100644 --- a/backend/config.go +++ b/backend/config.go @@ -17,8 +17,9 @@ type ServerConfig struct { } type AppConfig struct { - WindowWidth int - WindowHeight int + WindowWidth int + WindowHeight int + LastCheckedVersion string } type AlbumPageConfig struct { @@ -79,11 +80,12 @@ type Config struct { ReplayGain ReplayGainConfig } -func DefaultConfig() *Config { +func DefaultConfig(appVersionTag string) *Config { return &Config{ Application: AppConfig{ - WindowWidth: 1000, - WindowHeight: 800, + WindowWidth: 1000, + WindowHeight: 800, + LastCheckedVersion: appVersionTag, }, AlbumPage: AlbumPageConfig{ TracklistColumns: []string{"Artist", "Time", "Plays", "Favorite"}, @@ -123,14 +125,14 @@ func DefaultConfig() *Config { } } -func ReadConfigFile(filepath string) (*Config, error) { +func ReadConfigFile(filepath, appVersionTag string) (*Config, error) { f, err := os.Open(filepath) if err != nil { return nil, err } defer f.Close() - c := DefaultConfig() + c := DefaultConfig(appVersionTag) if err := toml.NewDecoder(f).Decode(c); err != nil { return nil, err } diff --git a/backend/servermanager.go b/backend/servermanager.go index bce9938..216207b 100644 --- a/backend/servermanager.go +++ b/backend/servermanager.go @@ -14,14 +14,15 @@ type ServerManager struct { ServerID uuid.UUID Server *subsonic.Client + appName string onServerConnected []func() onLogout []func() } var ErrUnreachable = errors.New("server is unreachable") -func NewServerManager() *ServerManager { - return &ServerManager{} +func NewServerManager(appName string) *ServerManager { + return &ServerManager{appName: appName} } func (s *ServerManager) ConnectToServer(conf *ServerConfig, password string) error { @@ -75,7 +76,7 @@ func (s *ServerManager) testConnectionAndCreateClient(hostname, username, passwo func (s *ServerManager) Logout() { if s.Server != nil { - keyring.Delete(AppName, s.ServerID.String()) + keyring.Delete(s.appName, s.ServerID.String()) for _, cb := range s.onLogout { cb() } @@ -93,9 +94,9 @@ func (s *ServerManager) OnLogout(cb func()) { } func (s *ServerManager) GetServerPassword(server *ServerConfig) (string, error) { - return keyring.Get(AppName, server.ID.String()) + return keyring.Get(s.appName, server.ID.String()) } func (s *ServerManager) SetServerPassword(server *ServerConfig, password string) error { - return keyring.Set(AppName, server.ID.String(), password) + return keyring.Set(s.appName, server.ID.String(), password) } diff --git a/backend/updatechecker.go b/backend/updatechecker.go new file mode 100644 index 0000000..6b23870 --- /dev/null +++ b/backend/updatechecker.go @@ -0,0 +1,71 @@ +package backend + +import ( + "context" + "log" + "net/http" + "strings" + "time" +) + +type UpdateChecker struct { + OnUpdatedVersionFound func(releaseURL string) + + foundUpdate bool + latestReleaseURL string + appVersionTag string + lastCheckedTag *string +} + +func NewUpdateChecker(appVersionTag, latestReleaseURL string, lastCheckedTag *string) UpdateChecker { + return UpdateChecker{ + appVersionTag: appVersionTag, + latestReleaseURL: latestReleaseURL, + lastCheckedTag: lastCheckedTag, + } +} + +func (u *UpdateChecker) Start(ctx context.Context, interval time.Duration) { + go func() { + u.checkForUpdate() // check once at startup + t := time.NewTicker(interval) + for { + select { + case <-ctx.Done(): + return + case <-t.C: + u.checkForUpdate() + } + } + }() +} + +func (u *UpdateChecker) UpdateAvailable() bool { + return u.foundUpdate +} + +func (u *UpdateChecker) checkForUpdate() { + t := u.latestVersionTag() + if t != "" && t != *u.lastCheckedTag { + u.foundUpdate = true + *u.lastCheckedTag = t + if u.OnUpdatedVersionFound != nil { + u.OnUpdatedVersionFound(u.latestReleaseURL) + } + } +} + +func (u *UpdateChecker) latestVersionTag() string { + resp, err := http.Head(u.latestReleaseURL) + if err != nil { + log.Printf("failed to check for newest version: %s", err.Error()) + return "" + } + url := resp.Request.URL.String() + url = strings.TrimSuffix(url, "/") + idx := strings.LastIndex(url, "/") + if idx >= len(url)-1 { + return "" + } + return url[idx+1:] +} diff --git a/backend/util/util.go b/backend/util/util.go index d7a24d7..43fe28b 100644 --- a/backend/util/util.go +++ b/backend/util/util.go @@ -2,11 +2,8 @@ package util import ( "io" - "log" "math/rand" - "net/http" "os" - "strings" "time" ) @@ -40,18 +37,3 @@ func CopyFile(srcPath, dstPath string) error { _, err = io.Copy(fout, fin) return err } - -func LatestVersionTag(latestReleaseURL string) string { - resp, err := http.Head(latestReleaseURL) - if err != nil { - log.Printf("failed to check for newest version: %s", err.Error()) - return "" - } - url := resp.Request.URL.String() - url = strings.TrimSuffix(url, "/") - idx := strings.LastIndex(url, "/") - if idx >= len(url)-1 { - return "" - } - return url[idx+1:] -} diff --git a/main.go b/main.go index cd30819..3ecff0e 100644 --- a/main.go +++ b/main.go @@ -14,10 +14,12 @@ import ( ) const ( - appname = "supersonic" - displayName = "Supersonic" - appVersion = "0.0.1-alpha2" - configFile = "config.toml" + appname = "supersonic" + displayName = "Supersonic" + appVersion = "0.0.1-alpha2" + appVersionTag = "v" + appVersion + configFile = "config.toml" + latestReleaseURL = "https://github.com/dweymouth/supersonic/releases/latest" ) func configPath() string { @@ -25,7 +27,7 @@ func configPath() string { } func main() { - myApp, err := backend.StartupApp() + myApp, err := backend.StartupApp(appname, appVersionTag, latestReleaseURL) if err != nil { log.Fatalf("fatal startup error: %v", err.Error()) }