package backend import ( "context" "errors" "fmt" "log" "os" "path" "supersonic/backend/util" "supersonic/player" "github.com/20after4/configdir" "github.com/zalando/go-keyring" ) const ( AppName = "supersonic" configFile = "config.toml" ) var ( ErrNoServers = errors.New("no servers set up") ) type App struct { Config *Config ServerManager *ServerManager ImageManager *ImageManager LibraryManager *LibraryManager PlaybackManager *PlaybackManager Player *player.Player bgrndCtx context.Context cancel context.CancelFunc } func StartupApp() (*App, error) { a := &App{} 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)) a.readConfig() if err := a.initMPV(); err != nil { return nil, err } a.Config.LocalPlayback.Volume = clamp(a.Config.LocalPlayback.Volume, 0, 100) a.Player.SetVolume(a.Config.LocalPlayback.Volume) a.ServerManager = NewServerManager() 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.LibraryManager.PreCacheCoverFn = func(coverID string) { _, _ = a.ImageManager.GetAlbumThumbnail(coverID) } return a, nil } func (a *App) readConfig() { configdir.MakePath(configdir.LocalConfig(AppName)) cfgPath := configPath() cfg, err := ReadConfigFile(cfgPath) if err != nil { log.Printf("Error reading app config file: %v", err) cfg = DefaultConfig() 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)) } } a.Config = cfg } func (a *App) initMPV() error { p := player.NewWithClientName(AppName) if err := p.Init(a.Config.LocalPlayback.AudioExclusive); err != nil { return fmt.Errorf("failed to initialize mpv player: %s", err.Error()) } a.Player = p return nil } func (a *App) LoginToDefaultServer() error { serverCfg := a.Config.GetDefaultServer() if serverCfg == nil { return ErrNoServers } pass, err := keyring.Get(AppName, serverCfg.ID.String()) if err != nil { return fmt.Errorf("error reading keyring credentials: %v", err) } return a.ServerManager.ConnectToServer(serverCfg, pass) } func (a *App) Shutdown() { a.Player.Stop() a.Config.LocalPlayback.Volume = a.Player.GetVolume() a.cancel() a.Player.Destroy() a.Config.WriteConfigFile(configPath()) } func configPath() string { return path.Join(configdir.LocalConfig(AppName), configFile) } func clamp(i, min, max int) int { if i < min { i = min } else if i > max { i = max } return i }