Skip to content

Commit

Permalink
Fix #371: add a portable mode option
Browse files Browse the repository at this point in the history
  • Loading branch information
dweymouth committed May 25, 2024
1 parent b15feee commit d08b563
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 32 deletions.
87 changes: 65 additions & 22 deletions backend/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log"
"os"
"path"
"path/filepath"
"reflect"
"slices"
"time"
Expand All @@ -23,10 +24,13 @@ import (
)

const (
configFile = "config.toml"
portableDir = "supersonic_portable"
sessionDir = "session"
sessionLockFile = ".lock"
sessionActivateFile = ".activate"
savedQueueFile = "saved_queue.json"
themesDir = "themes"
)

var (
Expand All @@ -49,7 +53,9 @@ type App struct {

appName string
appVersionTag string
configFile string
configDir string
cacheDir string
portableMode bool

isFirstLaunch bool // set by config file reader
bgrndCtx context.Context
Expand All @@ -62,8 +68,22 @@ func (a *App) VersionTag() string {
return a.appVersionTag
}

func StartupApp(appName, displayAppName, appVersionTag, configFile, latestReleaseURL string) (*App, error) {
sessionPath := configdir.LocalConfig(appName, sessionDir)
func StartupApp(appName, displayAppName, appVersionTag, latestReleaseURL string) (*App, error) {
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 {
log.Println("Another instance is running. Reactivating it...")
reactivateFile := path.Join(sessionPath, sessionActivateFile)
Expand All @@ -80,10 +100,16 @@ func StartupApp(appName, displayAppName, appVersionTag, configFile, latestReleas
}

log.Printf("Starting %s...", appName)
log.Printf("Using config dir: %s", configdir.LocalConfig(appName))
log.Printf("Using cache dir: %s", configdir.LocalCache(appName))

a := &App{appName: appName, appVersionTag: appVersionTag, configFile: configFile}
log.Printf("Using config dir: %s", confDir)
log.Printf("Using cache dir: %s", cacheDir)

a := &App{
appName: appName,
appVersionTag: appVersionTag,
configDir: confDir,
cacheDir: cacheDir,
portableMode: portableMode,
}
a.bgrndCtx, a.cancel = context.WithCancel(context.Background())
a.readConfig()
a.startConfigWriter(a.bgrndCtx)
Expand All @@ -109,9 +135,9 @@ func StartupApp(appName, displayAppName, appVersionTag, configFile, latestReleas
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.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.ImageManager.SetMaxOnDiskCacheSizeBytes(int64(a.Config.Application.MaxImageCacheSizeMB) * 1_048_576)
a.ServerManager.SetPrefetchAlbumCoverCallback(func(coverID string) {
Expand All @@ -132,9 +158,26 @@ func (a *App) IsFirstLaunch() bool {
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() {
configdir.MakePath(configdir.LocalConfig(a.appName))
cfgPath := a.configPath()
cfgPath := a.configFilePath()
var cfgExists bool
if _, err := os.Stat(cfgPath); err == nil {
cfgExists = true
Expand All @@ -145,9 +188,9 @@ func (a *App) readConfig() {
log.Printf("Error reading app config file: %v", err)
cfg = DefaultConfig(a.appVersionTag)
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)
_ = util.CopyFile(cfgPath, path.Join(configdir.LocalConfig(a.appName), backupCfgName))
_ = util.CopyFile(cfgPath, path.Join(a.configDir, backupCfgName))
}
}
a.Config = cfg
Expand Down Expand Up @@ -183,7 +226,7 @@ func (a *App) startConfigWriter(ctx context.Context) {
return
case <-tick.C:
if !reflect.DeepEqual(&a.lastWrittenCfg, a.Config) {
a.Config.WriteConfigFile(a.configPath())
a.Config.WriteConfigFile(a.configFilePath())
a.lastWrittenCfg = *a.Config
}
}
Expand Down Expand Up @@ -297,7 +340,7 @@ func (a *App) LoginToDefaultServer(string) 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)
return os.RemoveAll(path)
}
Expand All @@ -312,18 +355,18 @@ func (a *App) Shutdown() {
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.Config.LocalPlayback.Volume = a.LocalPlayer.GetVolume()
a.cancel()
a.LocalPlayer.Destroy()
a.Config.WriteConfigFile(a.configPath())
os.RemoveAll(configdir.LocalConfig(a.appName, sessionDir))
a.Config.WriteConfigFile(a.configFilePath())
os.RemoveAll(path.Join(a.configDir, sessionDir))
}

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)
if err != nil {
return err
Expand All @@ -346,12 +389,12 @@ func (a *App) LoadSavedPlayQueue() error {
}

func (a *App) SaveConfigFile() {
a.Config.WriteConfigFile(a.configPath())
a.Config.WriteConfigFile(a.configFilePath())
a.lastWrittenCfg = *a.Config
}

func (a *App) configPath() string {
return path.Join(configdir.LocalConfig(a.appName), a.configFile)
func (a *App) configFilePath() string {
return path.Join(a.configDir, configFile)
}

func clamp(i, min, max int) int {
Expand Down
19 changes: 14 additions & 5 deletions backend/servermanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type ServerManager struct {
ServerID uuid.UUID
Server mediaprovider.MediaProvider

useKeyring bool
prefetchCoverCB func(string)
appName string
config *Config
Expand All @@ -31,8 +32,8 @@ type ServerManager struct {

var ErrUnreachable = errors.New("server is unreachable")

func NewServerManager(appName string, config *Config) *ServerManager {
return &ServerManager{appName: appName, config: config}
func NewServerManager(appName string, config *Config, useKeyring bool) *ServerManager {
return &ServerManager{appName: appName, config: config, useKeyring: useKeyring}
}

func (s *ServerManager) SetPrefetchAlbumCoverCallback(cb func(string)) {
Expand Down Expand Up @@ -137,7 +138,9 @@ func (s *ServerManager) Logout(deletePassword bool) {
}

func (s *ServerManager) deleteServerPassword(serverID uuid.UUID) {
keyring.Delete(s.appName, s.ServerID.String())
if s.useKeyring {
keyring.Delete(s.appName, s.ServerID.String())
}
}

// Sets a callback that is invoked when a server is connected to.
Expand All @@ -151,11 +154,17 @@ func (s *ServerManager) OnLogout(cb func()) {
}

func (s *ServerManager) GetServerPassword(serverID uuid.UUID) (string, error) {
return keyring.Get(s.appName, serverID.String())
if s.useKeyring {
return keyring.Get(s.appName, serverID.String())
}
return "", errors.New("keyring not enabled")
}

func (s *ServerManager) SetServerPassword(server *ServerConfig, password string) error {
return keyring.Set(s.appName, server.ID.String(), password)
if s.useKeyring {
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) {
Expand Down
4 changes: 1 addition & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import (
"fyne.io/fyne/v2/app"
)

const configFile = "config.toml"

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 {
log.Fatalf("fatal startup error: %v", err.Error())
}
Expand Down
3 changes: 1 addition & 2 deletions ui/mainwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"strings"
"time"

"github.com/20after4/configdir"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/res"
Expand Down Expand Up @@ -60,7 +59,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
m := MainWindow{
App: app,
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
Expand Down

0 comments on commit d08b563

Please sign in to comment.