Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ type Config struct {
NotifyCfg NotifyConfig
ServerCfg ServerConfig
Flags Flags
PersistENV bool `env:"PERSIST" env-default:"true"`
Persist bool
ReplacePlaylistENV bool `env:"REPLACE_PLAYLIST" env-default:"true"`
ReplacePlaylist bool
System string `env:"EXPLO_SYSTEM"`
Debug bool `env:"DEBUG" env-default:"false"`
LogLevel string `env:"LOG_LEVEL" env-default:"INFO"`
Expand All @@ -37,8 +37,8 @@ type Flags struct {
PlaylistSet bool
DownloadMode string
ExcludeLocal bool
Persist bool
PersistSet bool
ReplacePlaylist bool
ReplacePlaylistSet bool
SearchMBID string
RefreshOnly bool
CleanDownloads bool
Expand Down Expand Up @@ -257,8 +257,8 @@ func (cfg *Config) HandleDeprecation() { //
slog.Warn("'DEBUG' variable is deprecated, please use LOG_LEVEL=DEBUG instead")
cfg.LogLevel = "DEBUG"
}
if cfg.Flags.PersistSet {
slog.Warn("--persist flag now only handles playlist deletion, use toggle in UI or --clean-downloads to delete tracks")
if cfg.Flags.ReplacePlaylistSet {
slog.Warn("--replace flag is deprecated, use the toggle in the UI instead")
}

if cfg.Flags.CleanDownloads && !cfg.DownloadCfg.UseSubDir {
Expand All @@ -269,7 +269,7 @@ func (cfg *Config) HandleDeprecation() { //
// Generate playlist name and description
func (cfg *Config) GenPlaylistDetails() {

cfg.ClientCfg.PlaylistName = getPlaylistName(cfg.Flags.Playlist, cfg.ClientCfg.PlaylistNFormat, cfg.Persist)
cfg.ClientCfg.PlaylistName = getPlaylistName(cfg.Flags.Playlist, cfg.ClientCfg.PlaylistNFormat, cfg.ReplacePlaylist)
cfg.ClientCfg.PlaylistDescr = fmt.Sprintf(
"Created for %s by Explo, using ListenBrainz recommendations.",
cfg.DiscoveryCfg.Listenbrainz.User)
Expand All @@ -282,14 +282,13 @@ func (cfg *Config) GenPlaylistDetails() {
}
}

func getPlaylistName(playlistType, format string, persist bool) string {

func getPlaylistName(playlistType, format string, replace bool) string {

toTitle := cases.Title(language.Und)
base := toTitle.String(playlistType)

// Non-persistent or custom playlists always use base name
if !persist || strings.HasPrefix(playlistType, "custom-") {
// When replacing or for custom playlists, always use base name
if replace || strings.HasPrefix(playlistType, "custom-") {
return base
}

Expand Down
16 changes: 8 additions & 8 deletions src/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (cfg *Config) GetFlags() error {
var playlist string
var downloadMode string
var excludeLocal bool
var persist bool
var replace bool
var showVersion bool
var searchMBID string
var refreshOnly bool
Expand All @@ -30,7 +30,7 @@ func (cfg *Config) GetFlags() error {
flag.StringVarP(&playlist, "playlist", "p", "weekly-exploration", "Playlist where to get tracks. Supported: weekly-exploration, weekly-jams, daily-jams, on-repeat")
flag.StringVarP(&downloadMode, "download-mode", "d", "normal", "Download mode: 'normal' (download only when track is not found locally), 'skip' (skip downloading, only use tracks already found locally), 'force' (always download, don't check for local tracks)")
flag.BoolVarP(&excludeLocal, "exclude-local", "e", false, "Exclude locally found tracks from the imported playlist")
flag.BoolVar(&persist, "persist", true, "Keep playlists between generations")
flag.BoolVar(&replace, "replace", true, "Replace existing playlist with the same name")
flag.BoolVarP(&showVersion, "version", "v", false, "Print version and exit")
flag.StringVar(&searchMBID, "search-mbid", "", "Test Plex search for a single recording MBID (resolves via ListenBrainz, then searches your library)")
flag.BoolVar(&refreshOnly, "refresh-only", false, "Trigger alibrary rescan and exit; skips discovery and downloads")
Expand All @@ -42,7 +42,7 @@ func (cfg *Config) GetFlags() error {
fmt.Println(Version)
os.Exit(0)
}
persistSet := flag.Lookup("persist").Changed
replaceSet := flag.Lookup("replace").Changed
cfgSet := flag.Lookup("config").Changed
playlistSet := flag.Lookup("playlist").Changed

Expand All @@ -65,13 +65,13 @@ func (cfg *Config) GetFlags() error {
cfg.Flags.PlaylistSet = playlistSet
cfg.Flags.DownloadMode = downloadMode
cfg.Flags.ExcludeLocal = excludeLocal
cfg.Flags.Persist = persist
cfg.Flags.ReplacePlaylist = replace
cfg.Flags.SearchMBID = searchMBID
cfg.Flags.RefreshOnly = refreshOnly
cfg.Flags.CleanDownloads = cleanDownloads

// for deprecation purposes (can be removed at a later date)
cfg.Flags.PersistSet = persistSet
cfg.Flags.ReplacePlaylistSet = replaceSet

return nil
}
Expand All @@ -84,10 +84,10 @@ func (cfg *Config) MergeFlags() {
cfg.ServerCfg.WebEnvPath = cfg.Flags.CfgPath
}

if cfg.Flags.PersistSet {
cfg.Persist = cfg.Flags.Persist
if cfg.Flags.ReplacePlaylistSet {
cfg.ReplacePlaylist = cfg.Flags.ReplacePlaylist
} else {
cfg.Persist = cfg.PersistENV
cfg.ReplacePlaylist = cfg.ReplacePlaylistENV
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func main() {
slog.Error(err.Error(), "notify", true)
os.Exit(1)
}
if !cfg.Persist {
if cfg.ReplacePlaylist {
err := client.DeletePlaylist()
if err != nil {
slog.Warn(err.Error(), "notify", true)
Expand Down
2 changes: 1 addition & 1 deletion src/web/backend/defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ var allConfigKeys = []string{
"ON_REPEAT_SCHEDULE", "ON_REPEAT_FLAGS",
"EXPLO_SYSTEM", "SYSTEM_URL", "API_KEY", "LIBRARY_NAME",
"SYSTEM_USERNAME", "SYSTEM_PASSWORD", "PLAYLIST_DIR", "SLEEP", "PUBLIC_PLAYLIST",
"DOWNLOAD_DIR", "USE_SUBDIRECTORY", "PATH_TEMPLATE", "ENRICH_TRACK_METADATA",
"DOWNLOAD_DIR", "USE_SUBDIRECTORY", "PATH_TEMPLATE", "ENRICH_TRACK_METADATA", "REPLACE_PLAYLIST",
"DOWNLOAD_SERVICES", "YOUTUBE_API_KEY", "TRACK_EXTENSION", "FILTER_LIST",
"SLSKD_URL", "SLSKD_API_KEY",
"WIZARD_COMPLETE", "MIGRATE_DOWNLOADS", "EXTENSIONS",
Expand Down
70 changes: 43 additions & 27 deletions src/web/backend/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,37 @@ func NewServer(cfg config.ServerConfig) *Server {
return s
}

// migratePersistEnv converts the old PERSIST env var to REPLACE_PLAYLIST (inverted).
// TODO: REMOVE THIS AFTER NEXT RELEASE — one-time migration for existing users.
func (s *Server) migratePersistEnv() {
data, err := os.ReadFile(s.cfg.WebEnvPath)
if err != nil {
return
}
env := parseEnvText(string(data))
if _, hasPersist := env["PERSIST"]; !hasPersist {
return
}
if _, hasReplace := env["REPLACE_PLAYLIST"]; hasReplace {
// Already migrated, just clean up old key
_ = updateEnvKeys(s.cfg.WebEnvPath, map[string]string{"PERSIST": ""}, web.SampleEnv)
return
}
// PERSIST=true (accumulate) → REPLACE_PLAYLIST=false
// PERSIST=false (replace) → REPLACE_PLAYLIST=true
val := "true"
if env["PERSIST"] == "true" {
val = "false"
}
_ = updateEnvKeys(s.cfg.WebEnvPath, map[string]string{
"REPLACE_PLAYLIST": val,
"PERSIST": "",
}, web.SampleEnv)
slog.Info("migrated PERSIST env var to REPLACE_PLAYLIST", "value", val)
}

func (s *Server) Start() error {
s.migratePersistEnv()
s.initServerLog()
s.startJobs()
coversDir := filepath.Join(s.cfg.WebDataDir, "cache", "covers")
Expand Down Expand Up @@ -245,7 +275,7 @@ func (s *Server) registerRoutes() {
s.mux.Handle("/api/ui/config/schedules", s.authStore.RequireAuth(http.HandlerFunc(s.handleSaveSchedule)))
s.mux.Handle("/api/ui/config/path-template", s.authStore.RequireAuth(http.HandlerFunc(s.handleSavePathTemplate)))
s.mux.Handle("/api/ui/config/enrich-metadata", s.authStore.RequireAuth(http.HandlerFunc(s.handleSaveEnrichMetadata)))
s.mux.Handle("/api/ui/config/persist", s.authStore.RequireAuth(http.HandlerFunc(s.handleSavePersist)))
s.mux.Handle("/api/ui/config/replace-playlist", s.authStore.RequireAuth(http.HandlerFunc(s.handleSaveReplacePlaylist)))
s.mux.Handle("/api/ui/config/clean-downloads", s.authStore.RequireAuth(http.HandlerFunc(s.handleSaveCleanDownloads)))

// Path template presets: GET list, POST add; DELETE per name under prefix
Expand Down Expand Up @@ -557,13 +587,10 @@ func (s *Server) handleSaveSchedule(w http.ResponseWriter, r *http.Request) {
return
}

// Carry over --persist=false / --clean-downloads if globally set
// Carry over --clean-downloads if globally set
data, _ := os.ReadFile(s.cfg.WebEnvPath)
for k, v := range parseEnvText(string(data)) {
if strings.HasSuffix(k, "_FLAGS") && v != "" {
if strings.Contains(v, "--persist=false") {
defaultFlags = addFlag(defaultFlags, "--persist=false")
}
if strings.Contains(v, "--clean-downloads") {
defaultFlags = addFlag(defaultFlags, "--clean-downloads")
}
Expand Down Expand Up @@ -643,9 +670,8 @@ func (s *Server) handleSaveEnrichMetadata(w http.ResponseWriter, r *http.Request
w.WriteHeader(http.StatusOK)
}

// handleSavePersist toggles persist by injecting/removing --persist=false
// from every active *_FLAGS entry, which is what start.sh feeds to the CLI.
func (s *Server) handleSavePersist(w http.ResponseWriter, r *http.Request) {
// handleSaveReplacePlaylist writes REPLACE_PLAYLIST=true/false to the .env file.
func (s *Server) handleSaveReplacePlaylist(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
Expand All @@ -657,17 +683,14 @@ func (s *Server) handleSavePersist(w http.ResponseWriter, r *http.Request) {
http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
return
}

if err := s.toggleFlagInEnv(!body.Enabled, "--persist=false"); err != nil {
val := "false"
if body.Enabled {
val = "true"
}
if err := updateEnvKeys(s.cfg.WebEnvPath, map[string]string{"REPLACE_PLAYLIST": val}, web.SampleEnv); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// Clean up the deprecated PERSIST env var if present
data, _ := os.ReadFile(s.cfg.WebEnvPath)
if _, ok := parseEnvText(string(data))["PERSIST"]; ok {
_ = updateEnvKeys(s.cfg.WebEnvPath, map[string]string{"PERSIST": ""}, web.SampleEnv)
}
w.WriteHeader(http.StatusOK)
}

Expand Down Expand Up @@ -753,7 +776,8 @@ func updateEnvKeys(path string, updates map[string]string, fallback []byte) erro
key = strings.TrimSpace(key)
if val, ok := updates[key]; ok {
if val == "" {
lines[i] = "" // remove by blanking
lines[i] = "" // remove by blanking
_ = os.Unsetenv(key) // drop stale copy cleanenv set at startup
} else {
lines[i] = key + "=" + formatEnvValue(val)
}
Expand Down Expand Up @@ -993,9 +1017,7 @@ func (s *Server) handleRun(w http.ResponseWriter, r *http.Request) {
return
}

args := buildArgs(r.FormValue("playlist"), r.FormValue("download_mode"),
r.FormValue("persist") == "false", r.FormValue("exclude_local") == "true",
s.cfg.WebEnvPath)
args := buildArgs(r.FormValue("playlist"), r.FormValue("download_mode"), s.cfg.WebEnvPath)

if err := s.startRun(args); err != nil {
if errors.Is(err, errRunAlreadyStarted) {
Expand Down Expand Up @@ -1303,19 +1325,13 @@ func (s *Server) unsubscribeRun(ch chan runEvent) {

// ── Helpers ────────────────────────────────────────────────────────────────

func buildArgs(playlist, downloadMode string, noPersist, excludeLocal bool, WebEnvPath string) []string {
func buildArgs(playlist, downloadMode, WebEnvPath string) []string {
args := []string{"--config", WebEnvPath}
if playlist != "" {
args = append(args, "--playlist", playlist)
}
if downloadMode != "" {
args = append(args, "--download-mode", downloadMode)
}
if noPersist {
args = append(args, "--persist=false")
}
if excludeLocal {
args = append(args, "--exclude-local")
}
return args
}
Loading
Loading