From 601a3854514eb2f0c5f23df4cf4b790f43e153a0 Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Mon, 14 Jul 2025 16:36:34 -0700 Subject: [PATCH] Add global cleanup policy configuration - Add global cleanup policy to main Config struct - Change per-feed cleanup policy to pointer for optional inheritance - Implement fallback logic to use global policy when feed doesn't specify its own - Add comprehensive tests for cleanup policy inheritance scenarios - Update configuration documentation with examples - Add nil check in cleanup function to handle feeds without cleanup policies Fixes #214 --- cmd/podsync/config.go | 7 +++ cmd/podsync/config_test.go | 109 +++++++++++++++++++++++++++++++++++++ config.toml.example | 9 ++- pkg/feed/config.go | 2 +- services/update/updater.go | 7 ++- 5 files changed, 131 insertions(+), 3 deletions(-) diff --git a/cmd/podsync/config.go b/cmd/podsync/config.go index 46f9d9fe..b972347d 100644 --- a/cmd/podsync/config.go +++ b/cmd/podsync/config.go @@ -35,6 +35,8 @@ type Config struct { Tokens map[model.Provider]StringSlice `toml:"tokens"` // Downloader (youtube-dl) configuration Downloader ytdl.Config `toml:"downloader"` + // Global cleanup policy applied to feeds that don't specify their own cleanup policy + Cleanup *feed.Cleanup `toml:"cleanup"` } type Log struct { @@ -179,6 +181,11 @@ func (c *Config) applyDefaults(configPath string) { if _feed.PlaylistSort == "" { _feed.PlaylistSort = model.SortingAsc } + + // Apply global cleanup policy if feed doesn't have its own + if _feed.Clean == nil && c.Cleanup != nil { + _feed.Clean = c.Cleanup + } } } diff --git a/cmd/podsync/config_test.go b/cmd/podsync/config_test.go index 15230ebe..aca66d72 100644 --- a/cmd/podsync/config_test.go +++ b/cmd/podsync/config_test.go @@ -84,6 +84,7 @@ timeout = 15 assert.EqualValues(t, 86400, feed.Filters.MaxDuration) assert.EqualValues(t, 365, feed.Filters.MaxAge) assert.EqualValues(t, 1, feed.Filters.MinAge) + require.NotNil(t, feed.Clean) assert.EqualValues(t, 10, feed.Clean.KeepLast) assert.EqualValues(t, model.SortingDesc, feed.PlaylistSort) @@ -233,6 +234,114 @@ data_dir = "/data" assert.True(t, config.Database.Badger.FileIO) } +func TestGlobalCleanupPolicy(t *testing.T) { + t.Run("global cleanup policy applied to feeds without cleanup", func(t *testing.T) { + const file = ` +[cleanup] +keep_last = 25 + +[server] +data_dir = "/data" + +[feeds] + [feeds.FEED1] + url = "https://youtube.com/channel/test1" + + [feeds.FEED2] + url = "https://youtube.com/channel/test2" + clean = { keep_last = 5 } +` + path := setup(t, file) + defer os.Remove(path) + + config, err := LoadConfig(path) + assert.NoError(t, err) + require.NotNil(t, config) + + // Global cleanup policy should be set + require.NotNil(t, config.Cleanup) + assert.EqualValues(t, 25, config.Cleanup.KeepLast) + + // FEED1 should inherit global cleanup policy + feed1, ok := config.Feeds["FEED1"] + assert.True(t, ok) + require.NotNil(t, feed1.Clean) + assert.EqualValues(t, 25, feed1.Clean.KeepLast) + + // FEED2 should keep its own cleanup policy + feed2, ok := config.Feeds["FEED2"] + assert.True(t, ok) + require.NotNil(t, feed2.Clean) + assert.EqualValues(t, 5, feed2.Clean.KeepLast) + }) + + t.Run("no global cleanup policy", func(t *testing.T) { + const file = ` +[server] +data_dir = "/data" + +[feeds] + [feeds.FEED1] + url = "https://youtube.com/channel/test1" + + [feeds.FEED2] + url = "https://youtube.com/channel/test2" + clean = { keep_last = 5 } +` + path := setup(t, file) + defer os.Remove(path) + + config, err := LoadConfig(path) + assert.NoError(t, err) + require.NotNil(t, config) + + // Global cleanup policy should not be set + assert.Nil(t, config.Cleanup) + + // FEED1 should have no cleanup policy + feed1, ok := config.Feeds["FEED1"] + assert.True(t, ok) + assert.Nil(t, feed1.Clean) + + // FEED2 should keep its own cleanup policy + feed2, ok := config.Feeds["FEED2"] + assert.True(t, ok) + require.NotNil(t, feed2.Clean) + assert.EqualValues(t, 5, feed2.Clean.KeepLast) + }) + + t.Run("feed cleanup overrides global cleanup", func(t *testing.T) { + const file = ` +[cleanup] +keep_last = 100 + +[server] +data_dir = "/data" + +[feeds] + [feeds.FEED1] + url = "https://youtube.com/channel/test1" + clean = { keep_last = 10 } +` + path := setup(t, file) + defer os.Remove(path) + + config, err := LoadConfig(path) + assert.NoError(t, err) + require.NotNil(t, config) + + // Global cleanup policy should be set + require.NotNil(t, config.Cleanup) + assert.EqualValues(t, 100, config.Cleanup.KeepLast) + + // FEED1 should use its own cleanup policy, not the global one + feed1, ok := config.Feeds["FEED1"] + assert.True(t, ok) + require.NotNil(t, feed1.Clean) + assert.EqualValues(t, 10, feed1.Clean.KeepLast) + }) +} + func setup(t *testing.T, file string) string { t.Helper() diff --git a/config.toml.example b/config.toml.example index 24874a13..611a8c46 100644 --- a/config.toml.example +++ b/config.toml.example @@ -1,5 +1,11 @@ # This is an example of TOML configuration file for Podsync. +# Global cleanup policy applied to feeds that don't specify their own cleanup policy. +# When set, this policy is used as a fallback for all feeds. +# Comment out or remove this section if you don't want a global cleanup policy. +[cleanup] +keep_last = 50 # Keep last 50 episodes globally (unless overridden per feed) + # Web server related configuration. [server] # HTTP server port. @@ -75,8 +81,9 @@ vimeo = [ # Multiple keys will be rotated. # If set then overwrite 'update_period'. cron_schedule = "@every 12h" - # Whether to cleanup old episodes. + # Whether to cleanup old episodes for this specific feed. # Keep last 10 episodes (order desc by PubDate) + # This overrides the global cleanup policy if one is set. clean = { keep_last = 10 } # Optional Golang regexp format. diff --git a/pkg/feed/config.go b/pkg/feed/config.go index 30c943d2..379b1ea4 100644 --- a/pkg/feed/config.go +++ b/pkg/feed/config.go @@ -33,7 +33,7 @@ type Config struct { // Only download episodes that match the filters (defaults to matching anything) Filters Filters `toml:"filters"` // Clean is a cleanup policy to use for this feed - Clean Cleanup `toml:"clean"` + Clean *Cleanup `toml:"clean"` // Custom is a list of feed customizations Custom Custom `toml:"custom"` // List of additional youtube-dl arguments passed at download time diff --git a/services/update/updater.go b/services/update/updater.go index efb89c28..fbe94321 100644 --- a/services/update/updater.go +++ b/services/update/updater.go @@ -327,11 +327,16 @@ func (u *Manager) cleanup(ctx context.Context, feedConfig *feed.Config) error { var ( feedID = feedConfig.ID logger = log.WithField("feed_id", feedID) - count = feedConfig.Clean.KeepLast list []*model.Episode result *multierror.Error ) + if feedConfig.Clean == nil { + logger.Debug("no cleanup policy configured") + return nil + } + + count := feedConfig.Clean.KeepLast if count < 1 { logger.Info("nothing to clean") return nil