Fix #371: add a portable mode option

This commit is contained in:
Drew Weymouth
2024-05-25 08:28:50 -07:00
parent b15feee85f
commit d08b563b8c
4 changed files with 80 additions and 31 deletions
+64 -21
View File
@@ -7,6 +7,7 @@ import (
"log" "log"
"os" "os"
"path" "path"
"path/filepath"
"reflect" "reflect"
"slices" "slices"
"time" "time"
@@ -23,10 +24,13 @@ import (
) )
const ( const (
configFile = "config.toml"
portableDir = "supersonic_portable"
sessionDir = "session" sessionDir = "session"
sessionLockFile = ".lock" sessionLockFile = ".lock"
sessionActivateFile = ".activate" sessionActivateFile = ".activate"
savedQueueFile = "saved_queue.json" savedQueueFile = "saved_queue.json"
themesDir = "themes"
) )
var ( var (
@@ -49,7 +53,9 @@ type App struct {
appName string appName string
appVersionTag string appVersionTag string
configFile string configDir string
cacheDir string
portableMode bool
isFirstLaunch bool // set by config file reader isFirstLaunch bool // set by config file reader
bgrndCtx context.Context bgrndCtx context.Context
@@ -62,8 +68,22 @@ func (a *App) VersionTag() string {
return a.appVersionTag return a.appVersionTag
} }
func StartupApp(appName, displayAppName, appVersionTag, configFile, latestReleaseURL string) (*App, error) { func StartupApp(appName, displayAppName, appVersionTag, latestReleaseURL string) (*App, error) {
sessionPath := configdir.LocalConfig(appName, sessionDir) var confDir, cacheDir string
portableMode := false
if p := checkPortablePath(); p != "" {
confDir = path.Join(p, "config")
cacheDir = path.Join(p, "cache")
portableMode = true
} else {
confDir = configdir.LocalConfig(appName)
cacheDir = configdir.LocalCache(appName)
}
// ensure config and cache dirs exist
configdir.MakePath(confDir)
configdir.MakePath(cacheDir)
sessionPath := path.Join(confDir, sessionDir)
if _, err := os.Stat(path.Join(sessionPath, sessionLockFile)); err == nil { if _, err := os.Stat(path.Join(sessionPath, sessionLockFile)); err == nil {
log.Println("Another instance is running. Reactivating it...") log.Println("Another instance is running. Reactivating it...")
reactivateFile := path.Join(sessionPath, sessionActivateFile) reactivateFile := path.Join(sessionPath, sessionActivateFile)
@@ -80,10 +100,16 @@ func StartupApp(appName, displayAppName, appVersionTag, configFile, latestReleas
} }
log.Printf("Starting %s...", appName) log.Printf("Starting %s...", appName)
log.Printf("Using config dir: %s", configdir.LocalConfig(appName)) log.Printf("Using config dir: %s", confDir)
log.Printf("Using cache dir: %s", configdir.LocalCache(appName)) log.Printf("Using cache dir: %s", cacheDir)
a := &App{appName: appName, appVersionTag: appVersionTag, configFile: configFile} a := &App{
appName: appName,
appVersionTag: appVersionTag,
configDir: confDir,
cacheDir: cacheDir,
portableMode: portableMode,
}
a.bgrndCtx, a.cancel = context.WithCancel(context.Background()) a.bgrndCtx, a.cancel = context.WithCancel(context.Background())
a.readConfig() a.readConfig()
a.startConfigWriter(a.bgrndCtx) a.startConfigWriter(a.bgrndCtx)
@@ -109,9 +135,9 @@ func StartupApp(appName, displayAppName, appVersionTag, configFile, latestReleas
return nil, err return nil, err
} }
a.ServerManager = NewServerManager(appName, a.Config) a.ServerManager = NewServerManager(appName, a.Config, !portableMode /*use keyring*/)
a.PlaybackManager = NewPlaybackManager(a.bgrndCtx, a.ServerManager, a.LocalPlayer, &a.Config.Scrobbling, &a.Config.Transcoding) a.PlaybackManager = NewPlaybackManager(a.bgrndCtx, a.ServerManager, a.LocalPlayer, &a.Config.Scrobbling, &a.Config.Transcoding)
a.ImageManager = NewImageManager(a.bgrndCtx, a.ServerManager, configdir.LocalCache(a.appName)) a.ImageManager = NewImageManager(a.bgrndCtx, a.ServerManager, cacheDir)
a.Config.Application.MaxImageCacheSizeMB = clamp(a.Config.Application.MaxImageCacheSizeMB, 1, 500) a.Config.Application.MaxImageCacheSizeMB = clamp(a.Config.Application.MaxImageCacheSizeMB, 1, 500)
a.ImageManager.SetMaxOnDiskCacheSizeBytes(int64(a.Config.Application.MaxImageCacheSizeMB) * 1_048_576) a.ImageManager.SetMaxOnDiskCacheSizeBytes(int64(a.Config.Application.MaxImageCacheSizeMB) * 1_048_576)
a.ServerManager.SetPrefetchAlbumCoverCallback(func(coverID string) { a.ServerManager.SetPrefetchAlbumCoverCallback(func(coverID string) {
@@ -132,9 +158,26 @@ func (a *App) IsFirstLaunch() bool {
return a.isFirstLaunch return a.isFirstLaunch
} }
func (a *App) IsPortableMode() bool {
return a.portableMode
}
func (a *App) ThemesDir() string {
return filepath.Join(a.configDir, themesDir)
}
func checkPortablePath() string {
if p, err := os.Executable(); err == nil {
pdirPath := path.Join(filepath.Dir(p), portableDir)
if s, err := os.Stat(pdirPath); err == nil && s.IsDir() {
return pdirPath
}
}
return ""
}
func (a *App) readConfig() { func (a *App) readConfig() {
configdir.MakePath(configdir.LocalConfig(a.appName)) cfgPath := a.configFilePath()
cfgPath := a.configPath()
var cfgExists bool var cfgExists bool
if _, err := os.Stat(cfgPath); err == nil { if _, err := os.Stat(cfgPath); err == nil {
cfgExists = true cfgExists = true
@@ -145,9 +188,9 @@ func (a *App) readConfig() {
log.Printf("Error reading app config file: %v", err) log.Printf("Error reading app config file: %v", err)
cfg = DefaultConfig(a.appVersionTag) cfg = DefaultConfig(a.appVersionTag)
if cfgExists { if cfgExists {
backupCfgName := fmt.Sprintf("%s.bak", a.configFile) backupCfgName := fmt.Sprintf("%s.bak", configFile)
log.Printf("Config file may be malformed: copying to %s", backupCfgName) log.Printf("Config file may be malformed: copying to %s", backupCfgName)
_ = util.CopyFile(cfgPath, path.Join(configdir.LocalConfig(a.appName), backupCfgName)) _ = util.CopyFile(cfgPath, path.Join(a.configDir, backupCfgName))
} }
} }
a.Config = cfg a.Config = cfg
@@ -183,7 +226,7 @@ func (a *App) startConfigWriter(ctx context.Context) {
return return
case <-tick.C: case <-tick.C:
if !reflect.DeepEqual(&a.lastWrittenCfg, a.Config) { if !reflect.DeepEqual(&a.lastWrittenCfg, a.Config) {
a.Config.WriteConfigFile(a.configPath()) a.Config.WriteConfigFile(a.configFilePath())
a.lastWrittenCfg = *a.Config a.lastWrittenCfg = *a.Config
} }
} }
@@ -297,7 +340,7 @@ func (a *App) LoginToDefaultServer(string) error {
} }
func (a *App) DeleteServerCacheDir(serverID uuid.UUID) error { func (a *App) DeleteServerCacheDir(serverID uuid.UUID) error {
path := path.Join(configdir.LocalCache(a.appName), serverID.String()) path := path.Join(a.cacheDir, serverID.String())
log.Printf("Deleting server cache dir: %s", path) log.Printf("Deleting server cache dir: %s", path)
return os.RemoveAll(path) return os.RemoveAll(path)
} }
@@ -312,18 +355,18 @@ func (a *App) Shutdown() {
queueServer = qs queueServer = qs
} }
} }
SavePlayQueue(a.ServerManager.ServerID.String(), a.PlaybackManager, configdir.LocalConfig(a.appName, savedQueueFile), queueServer) SavePlayQueue(a.ServerManager.ServerID.String(), a.PlaybackManager, path.Join(a.configDir, savedQueueFile), queueServer)
} }
a.PlaybackManager.Stop() // will trigger scrobble check a.PlaybackManager.Stop() // will trigger scrobble check
a.Config.LocalPlayback.Volume = a.LocalPlayer.GetVolume() a.Config.LocalPlayback.Volume = a.LocalPlayer.GetVolume()
a.cancel() a.cancel()
a.LocalPlayer.Destroy() a.LocalPlayer.Destroy()
a.Config.WriteConfigFile(a.configPath()) a.Config.WriteConfigFile(a.configFilePath())
os.RemoveAll(configdir.LocalConfig(a.appName, sessionDir)) os.RemoveAll(path.Join(a.configDir, sessionDir))
} }
func (a *App) LoadSavedPlayQueue() error { func (a *App) LoadSavedPlayQueue() error {
queueFilePath := configdir.LocalConfig(a.appName, savedQueueFile) queueFilePath := path.Join(a.configDir, savedQueueFile)
queue, err := LoadPlayQueue(queueFilePath, a.ServerManager, a.Config.Application.SaveQueueToServer) queue, err := LoadPlayQueue(queueFilePath, a.ServerManager, a.Config.Application.SaveQueueToServer)
if err != nil { if err != nil {
return err return err
@@ -346,12 +389,12 @@ func (a *App) LoadSavedPlayQueue() error {
} }
func (a *App) SaveConfigFile() { func (a *App) SaveConfigFile() {
a.Config.WriteConfigFile(a.configPath()) a.Config.WriteConfigFile(a.configFilePath())
a.lastWrittenCfg = *a.Config a.lastWrittenCfg = *a.Config
} }
func (a *App) configPath() string { func (a *App) configFilePath() string {
return path.Join(configdir.LocalConfig(a.appName), a.configFile) return path.Join(a.configDir, configFile)
} }
func clamp(i, min, max int) int { func clamp(i, min, max int) int {
+11 -2
View File
@@ -22,6 +22,7 @@ type ServerManager struct {
ServerID uuid.UUID ServerID uuid.UUID
Server mediaprovider.MediaProvider Server mediaprovider.MediaProvider
useKeyring bool
prefetchCoverCB func(string) prefetchCoverCB func(string)
appName string appName string
config *Config config *Config
@@ -31,8 +32,8 @@ type ServerManager struct {
var ErrUnreachable = errors.New("server is unreachable") var ErrUnreachable = errors.New("server is unreachable")
func NewServerManager(appName string, config *Config) *ServerManager { func NewServerManager(appName string, config *Config, useKeyring bool) *ServerManager {
return &ServerManager{appName: appName, config: config} return &ServerManager{appName: appName, config: config, useKeyring: useKeyring}
} }
func (s *ServerManager) SetPrefetchAlbumCoverCallback(cb func(string)) { func (s *ServerManager) SetPrefetchAlbumCoverCallback(cb func(string)) {
@@ -137,8 +138,10 @@ func (s *ServerManager) Logout(deletePassword bool) {
} }
func (s *ServerManager) deleteServerPassword(serverID uuid.UUID) { func (s *ServerManager) deleteServerPassword(serverID uuid.UUID) {
if s.useKeyring {
keyring.Delete(s.appName, s.ServerID.String()) keyring.Delete(s.appName, s.ServerID.String())
} }
}
// Sets a callback that is invoked when a server is connected to. // Sets a callback that is invoked when a server is connected to.
func (s *ServerManager) OnServerConnected(cb func()) { func (s *ServerManager) OnServerConnected(cb func()) {
@@ -151,12 +154,18 @@ func (s *ServerManager) OnLogout(cb func()) {
} }
func (s *ServerManager) GetServerPassword(serverID uuid.UUID) (string, error) { func (s *ServerManager) GetServerPassword(serverID uuid.UUID) (string, error) {
if s.useKeyring {
return keyring.Get(s.appName, serverID.String()) return keyring.Get(s.appName, serverID.String())
} }
return "", errors.New("keyring not enabled")
}
func (s *ServerManager) SetServerPassword(server *ServerConfig, password string) error { func (s *ServerManager) SetServerPassword(server *ServerConfig, password string) error {
if s.useKeyring {
return keyring.Set(s.appName, server.ID.String(), password) return keyring.Set(s.appName, server.ID.String(), password)
} }
return errors.New("keyring not available")
}
func (s *ServerManager) connect(connection ServerConnection, password string) (mediaprovider.Server, error) { func (s *ServerManager) connect(connection ServerConnection, password string) (mediaprovider.Server, error) {
var cli, altCli mediaprovider.Server var cli, altCli mediaprovider.Server
+1 -3
View File
@@ -14,10 +14,8 @@ import (
"fyne.io/fyne/v2/app" "fyne.io/fyne/v2/app"
) )
const configFile = "config.toml"
func main() { func main() {
myApp, err := backend.StartupApp(res.AppName, res.DisplayName, res.AppVersionTag, configFile, res.LatestReleaseURL) myApp, err := backend.StartupApp(res.AppName, res.DisplayName, res.AppVersionTag, res.LatestReleaseURL)
if err != nil { if err != nil {
log.Fatalf("fatal startup error: %v", err.Error()) log.Fatalf("fatal startup error: %v", err.Error())
} }
+1 -2
View File
@@ -7,7 +7,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/20after4/configdir"
"github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/res"
@@ -60,7 +59,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
m := MainWindow{ m := MainWindow{
App: app, App: app,
Window: fyneApp.NewWindow(displayAppName), Window: fyneApp.NewWindow(displayAppName),
theme: theme.NewMyTheme(&app.Config.Theme, configdir.LocalConfig(appName, "themes")), theme: theme.NewMyTheme(&app.Config.Theme, app.ThemesDir()),
} }
m.theme.NormalFont = app.Config.Application.FontNormalTTF m.theme.NormalFont = app.Config.Application.FontNormalTTF