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 <noreply@anthropic.com>
This commit is contained in:
ex-takashima 2026-02-22 21:34:58 +09:00
parent 81df10b0b1
commit f0484cb6df
5 changed files with 43 additions and 15 deletions

View file

@ -193,8 +193,14 @@ func gatewayCmd() {
fmt.Println("✓ Device event service started")
}
mediaCleaner := utils.NewMediaCleaner()
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")

View file

@ -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"`
MediaCleanup MediaCleanupConfig `json:"media_cleanup"`
}
type SkillsToolsConfig struct {

View file

@ -303,6 +303,11 @@ func DefaultConfig() *Config {
TTLSeconds: 300,
},
},
MediaCleanup: MediaCleanupConfig{
Enabled: true,
MaxAge: 30,
Interval: 5,
},
},
Heartbeat: HeartbeatConfig{
Enabled: true,

View file

@ -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(),
})
})
}

View file

@ -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()