From f0484cb6dffd2bf899380f4f6344b432b1c5b4e9 Mon Sep 17 00:00:00 2001 From: ex-takashima Date: Sun, 22 Feb 2026 21:34:58 +0900 Subject: [PATCH] feat: make MediaCleaner configurable via tools.media_cleanup Add MediaCleanupConfig with enabled, max_age_minutes, and interval_minutes fields. MediaCleaner is only started when enabled (default: true) and logs its effective settings at startup. Defaults: interval=5m, max_age=30m. Operators can tune or disable cleanup for deployments with longer async processing times. Co-Authored-By: Claude Opus 4.6 --- cmd/picoclaw/cmd_gateway.go | 11 ++++++++--- pkg/config/config.go | 15 +++++++++++---- pkg/config/defaults.go | 5 +++++ pkg/utils/media.go | 23 +++++++++++++++++------ pkg/utils/media_test.go | 4 ++-- 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/cmd/picoclaw/cmd_gateway.go b/cmd/picoclaw/cmd_gateway.go index d299fb2d3..c38891277 100644 --- a/cmd/picoclaw/cmd_gateway.go +++ b/cmd/picoclaw/cmd_gateway.go @@ -193,8 +193,14 @@ func gatewayCmd() { fmt.Println("✓ Device event service started") } - mediaCleaner := utils.NewMediaCleaner() - mediaCleaner.Start() + if cfg.Tools.MediaCleanup.Enabled { + mediaCleaner := utils.NewMediaCleaner( + cfg.Tools.MediaCleanup.Interval, + cfg.Tools.MediaCleanup.MaxAge, + ) + mediaCleaner.Start() + defer mediaCleaner.Stop() + } if err := channelManager.StartAll(ctx); err != nil { fmt.Printf("Error starting channels: %v\n", err) @@ -220,7 +226,6 @@ func gatewayCmd() { deviceService.Stop() heartbeatService.Stop() cronService.Stop() - mediaCleaner.Stop() agentLoop.Stop() channelManager.StopAll(ctx) fmt.Println("✓ Gateway stopped") diff --git a/pkg/config/config.go b/pkg/config/config.go index 036021e49..862f5742a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -452,11 +452,18 @@ type ExecConfig struct { CustomDenyPatterns []string `json:"custom_deny_patterns" env:"PICOCLAW_TOOLS_EXEC_CUSTOM_DENY_PATTERNS"` } +type MediaCleanupConfig struct { + Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_MEDIA_CLEANUP_ENABLED"` + MaxAge int `json:"max_age_minutes" env:"PICOCLAW_TOOLS_MEDIA_CLEANUP_MAX_AGE_MINUTES"` + Interval int `json:"interval_minutes" env:"PICOCLAW_TOOLS_MEDIA_CLEANUP_INTERVAL_MINUTES"` +} + type ToolsConfig struct { - Web WebToolsConfig `json:"web"` - Cron CronToolsConfig `json:"cron"` - Exec ExecConfig `json:"exec"` - Skills SkillsToolsConfig `json:"skills"` + Web WebToolsConfig `json:"web"` + Cron CronToolsConfig `json:"cron"` + Exec ExecConfig `json:"exec"` + Skills SkillsToolsConfig `json:"skills"` + MediaCleanup MediaCleanupConfig `json:"media_cleanup"` } type SkillsToolsConfig struct { diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 7654326e7..40f9ee021 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -303,6 +303,11 @@ func DefaultConfig() *Config { TTLSeconds: 300, }, }, + MediaCleanup: MediaCleanupConfig{ + Enabled: true, + MaxAge: 30, + Interval: 5, + }, }, Heartbeat: HeartbeatConfig{ Enabled: true, diff --git a/pkg/utils/media.go b/pkg/utils/media.go index 79bc7217d..0ac4be001 100644 --- a/pkg/utils/media.go +++ b/pkg/utils/media.go @@ -154,12 +154,20 @@ type MediaCleaner struct { once sync.Once } -// NewMediaCleaner creates a new MediaCleaner with default settings -// (scan every 5 minutes, remove files older than 30 minutes). -func NewMediaCleaner() *MediaCleaner { +// NewMediaCleaner creates a new MediaCleaner with the given settings. +// If intervalMinutes or maxAgeMinutes are <= 0, defaults are used (5 and 30 respectively). +func NewMediaCleaner(intervalMinutes, maxAgeMinutes int) *MediaCleaner { + interval := time.Duration(intervalMinutes) * time.Minute + if interval <= 0 { + interval = 5 * time.Minute + } + maxAge := time.Duration(maxAgeMinutes) * time.Minute + if maxAge <= 0 { + maxAge = 30 * time.Minute + } return &MediaCleaner{ - interval: 5 * time.Minute, - maxAge: 30 * time.Minute, + interval: interval, + maxAge: maxAge, stop: make(chan struct{}), } } @@ -168,7 +176,10 @@ func NewMediaCleaner() *MediaCleaner { func (mc *MediaCleaner) Start() { mc.once.Do(func() { go mc.loop() - logger.InfoC("media", "Media cleaner started") + logger.InfoCF("media", "Media cleaner started", map[string]any{ + "interval": mc.interval.String(), + "max_age": mc.maxAge.String(), + }) }) } diff --git a/pkg/utils/media_test.go b/pkg/utils/media_test.go index e77908056..90a93ffa9 100644 --- a/pkg/utils/media_test.go +++ b/pkg/utils/media_test.go @@ -35,7 +35,7 @@ func TestMediaCleanerRemovesOldFiles(t *testing.T) { defer os.Remove(newFile) // Run cleanup directly - mc := NewMediaCleaner() + mc := NewMediaCleaner(5, 30) mc.cleanup() // Old file should be gone @@ -50,7 +50,7 @@ func TestMediaCleanerRemovesOldFiles(t *testing.T) { } func TestMediaCleanerStartStop(t *testing.T) { - mc := NewMediaCleaner() + mc := NewMediaCleaner(5, 30) // Start should not panic mc.Start()