add monitor sync jobs and duplicate-safe qobuz updates
This commit is contained in:
@@ -13,34 +13,37 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Command string
|
||||
SpotifyClientID string
|
||||
SpotifyRedirect string
|
||||
SpotifyScopes []string
|
||||
SpotifyManual bool
|
||||
SessionPath string
|
||||
RememberCreds bool
|
||||
QobuzUsername string
|
||||
QobuzPassword string
|
||||
QobuzAppID string
|
||||
QobuzAppSecret string
|
||||
QobuzSelfTest bool
|
||||
QobuzTestWrite bool
|
||||
QobuzTestQuery string
|
||||
Monitor bool
|
||||
MonitorOnce bool
|
||||
MonitorTransfer bool
|
||||
MonitorInterval time.Duration
|
||||
LikedPlaylist string
|
||||
DryRun bool
|
||||
ReportPath string
|
||||
Concurrency int
|
||||
PlaylistNames []string
|
||||
PlaylistURLs []string
|
||||
AllPlaylists bool
|
||||
IncludeLiked bool
|
||||
NonInteractive bool
|
||||
PublicPlaylists bool
|
||||
Command string
|
||||
ConfigFile string
|
||||
SpotifyClientID string
|
||||
SpotifyRedirect string
|
||||
SpotifyScopes []string
|
||||
SpotifyManual bool
|
||||
SessionPath string
|
||||
RememberCreds bool
|
||||
QobuzUsername string
|
||||
QobuzPassword string
|
||||
QobuzAppID string
|
||||
QobuzAppSecret string
|
||||
QobuzSelfTest bool
|
||||
QobuzTestWrite bool
|
||||
QobuzTestQuery string
|
||||
Monitor bool
|
||||
MonitorOnce bool
|
||||
MonitorTransfer bool
|
||||
MonitorInterval time.Duration
|
||||
SyncMode string
|
||||
TargetPlaylistID int64
|
||||
LikedPlaylist string
|
||||
DryRun bool
|
||||
ReportPath string
|
||||
Concurrency int
|
||||
PlaylistNames []string
|
||||
PlaylistURLs []string
|
||||
AllPlaylists bool
|
||||
IncludeLiked bool
|
||||
NonInteractive bool
|
||||
PublicPlaylists bool
|
||||
}
|
||||
|
||||
type multiFlag []string
|
||||
@@ -80,6 +83,7 @@ func Load() (Config, error) {
|
||||
defaultAppSecret := envOr("QOBUZ_APP_SECRET", "e79f8b9be485692b0e5f9dd895826368")
|
||||
defaultConcurrency := envIntOr("QTRANSFER_CONCURRENCY", 4)
|
||||
|
||||
flag.StringVar(&cfg.ConfigFile, "config", envOr("QTRANSFER_CONFIG", ""), "Path to TOML config file with playlist sync jobs")
|
||||
flag.StringVar(&cfg.SpotifyClientID, "spotify-client-id", envOr("SPOTIFY_CLIENT_ID", ""), "Spotify app client ID")
|
||||
flag.StringVar(&cfg.SpotifyRedirect, "spotify-redirect-uri", envOr("SPOTIFY_REDIRECT_URI", "http://127.0.0.1:8888/callback"), "Spotify OAuth redirect URI")
|
||||
scopes := flag.String("spotify-scopes", envOr("SPOTIFY_SCOPES", defaultScopes), "Comma-separated Spotify OAuth scopes")
|
||||
@@ -96,8 +100,10 @@ func Load() (Config, error) {
|
||||
flag.StringVar(&cfg.QobuzTestQuery, "qobuz-self-test-query", envOr("QTRANSFER_QOBUZ_SELF_TEST_QUERY", "Daft Punk One More Time"), "Search query used for --qobuz-self-test")
|
||||
flag.BoolVar(&cfg.Monitor, "monitor", envBoolOr("QTRANSFER_MONITOR", false), "Monitor selected playlists for updates")
|
||||
flag.BoolVar(&cfg.MonitorOnce, "monitor-once", envBoolOr("QTRANSFER_MONITOR_ONCE", false), "Run a single monitor check then exit")
|
||||
flag.BoolVar(&cfg.MonitorTransfer, "monitor-transfer", envBoolOr("QTRANSFER_MONITOR_TRANSFER", false), "When monitoring, transfer playlists that changed")
|
||||
flag.BoolVar(&cfg.MonitorTransfer, "monitor-transfer", envBoolOr("QTRANSFER_MONITOR_TRANSFER", false), "When monitoring, sync changed playlists to Qobuz")
|
||||
flag.DurationVar(&cfg.MonitorInterval, "monitor-interval", envDurationOr("QTRANSFER_MONITOR_INTERVAL", 5*time.Minute), "Monitor polling interval (e.g. 2m, 30s)")
|
||||
flag.StringVar(&cfg.SyncMode, "sync-mode", strings.ToLower(envOr("QTRANSFER_SYNC_MODE", "append")), "Sync mode for monitor-transfer: append or mirror")
|
||||
flag.Int64Var(&cfg.TargetPlaylistID, "target-playlist-id", envInt64Or("QTRANSFER_TARGET_PLAYLIST_ID", 0), "Bind source playlist (single URL only) to existing Qobuz playlist ID")
|
||||
|
||||
flag.BoolVar(&cfg.DryRun, "dry-run", envBoolOr("QTRANSFER_DRY_RUN", false), "Resolve matches only, do not create or mutate Qobuz playlists")
|
||||
flag.StringVar(&cfg.ReportPath, "report", envOr("QTRANSFER_REPORT", "transfer-report.json"), "Report output path")
|
||||
@@ -119,6 +125,8 @@ func Load() (Config, error) {
|
||||
cfg.PlaylistNames = playlists
|
||||
cfg.PlaylistURLs = playlistURLs
|
||||
cfg.SpotifyScopes = splitComma(*scopes)
|
||||
cfg.SyncMode = strings.ToLower(strings.TrimSpace(cfg.SyncMode))
|
||||
cfg.SyncMode = strings.ToLower(strings.TrimSpace(cfg.SyncMode))
|
||||
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
@@ -144,6 +152,15 @@ func (c Config) Validate() error {
|
||||
if c.MonitorInterval < 2*time.Second {
|
||||
return fmt.Errorf("monitor interval must be at least 2s")
|
||||
}
|
||||
if c.SyncMode != "append" && c.SyncMode != "mirror" {
|
||||
return fmt.Errorf("invalid sync mode %q (expected append or mirror)", c.SyncMode)
|
||||
}
|
||||
if c.TargetPlaylistID < 0 {
|
||||
return fmt.Errorf("target-playlist-id must be >= 0")
|
||||
}
|
||||
if c.TargetPlaylistID > 0 && !c.Monitor {
|
||||
return fmt.Errorf("target-playlist-id is currently supported with --monitor mode")
|
||||
}
|
||||
if strings.TrimSpace(c.SessionPath) == "" {
|
||||
return fmt.Errorf("session file path cannot be empty")
|
||||
}
|
||||
@@ -192,6 +209,18 @@ func envIntOr(key string, fallback int) int {
|
||||
return n
|
||||
}
|
||||
|
||||
func envInt64Or(key string, fallback int64) int64 {
|
||||
v := strings.TrimSpace(os.Getenv(key))
|
||||
if v == "" {
|
||||
return fallback
|
||||
}
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func envBoolOr(key string, fallback bool) bool {
|
||||
v := strings.TrimSpace(os.Getenv(key))
|
||||
if v == "" {
|
||||
|
||||
Reference in New Issue
Block a user