feat(library): add server-driven file deletion with allow_delete config

This commit is contained in:
Deivid Soto 2026-04-10 16:35:12 +02:00
parent 8ad8a5ea47
commit f699b26fa6
9 changed files with 744 additions and 24 deletions

View file

@ -14,7 +14,7 @@ import (
"github.com/torrentclaw/unarr/internal/config"
)
var configCategories = []string{"downloads", "organization", "notifications", "device", "region", "connection", "advanced"}
var configCategories = []string{"downloads", "organization", "library", "notifications", "device", "region", "connection", "advanced"}
func newConfigCmd() *cobra.Command {
cmd := &cobra.Command{
@ -25,6 +25,7 @@ func newConfigCmd() *cobra.Command {
Categories:
downloads Download directory, method, speed limits, concurrency
organization Auto-sort into Movies / TV Shows folders
library Library scan settings and file deletion permissions
notifications Desktop notifications
device Agent name
region Country and language
@ -95,6 +96,7 @@ func runConfigMenu(category string) error {
Options(
huh.NewOption("Downloads — directory, method, speed limits", "downloads"),
huh.NewOption("Organization — auto-sort Movies & TV Shows", "organization"),
huh.NewOption("Library — scan settings & file deletion", "library"),
huh.NewOption("Notifications — desktop notifications", "notifications"),
huh.NewOption("Device — agent name", "device"),
huh.NewOption("Region — country & language", "region"),
@ -131,6 +133,8 @@ func runCategory(cfg *config.Config, category string) error {
return configDownloads(cfg)
case "organization":
return configOrganization(cfg)
case "library":
return configLibrary(cfg)
case "notifications":
return configNotifications(cfg)
case "device":
@ -311,6 +315,17 @@ func configConnection(cfg *config.Config) error {
).Run()
}
func configLibrary(cfg *config.Config) error {
return huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Allow file deletion from web UI?").
Description("When enabled, the web library's Delete button can permanently remove files from disk.\nOnly activate this if you understand that deleted files cannot be recovered.").
Value(&cfg.Library.AllowDelete),
),
).Run()
}
func configAdvanced(_ *config.Config) error {
// Sync intervals are adaptive (3s watching, 60s idle) — no user-facing config needed.
fmt.Println("No advanced settings to configure. Sync intervals are automatic.")

View file

@ -138,6 +138,8 @@ func runDaemonStart() error {
StreamPort: cfg.Download.StreamPort,
LanIP: engine.LanIP(),
TailscaleIP: engine.TailscaleIP(),
CanDelete: cfg.Library.AllowDelete,
ScanPaths: library.ResolveScanPaths(cfg.Download.Dir, cfg.Organize.MoviesDir, cfg.Organize.TVShowsDir, cfg.Library.ScanPath),
}
// Create HTTP client — single communication channel
@ -302,6 +304,13 @@ func runDaemonStart() error {
}
}
// Wire: sync receives file deletion requests from the server
if cfg.Library.AllowDelete && len(daemonCfg.ScanPaths) > 0 {
sc.OnDeleteFiles = func(items []agent.LibraryDeleteRequest) []int {
return library.DeleteFiles(items, daemonCfg.ScanPaths)
}
}
// Wire: sync receives stream requests for completed downloads
d.OnStreamRequested = func(sr agent.StreamRequest) {
if streamSrv.CurrentTaskID() == sr.TaskID {
@ -401,7 +410,7 @@ func runDaemonStart() error {
}()
// Start auto-scan goroutine
scanPaths := library.ResolveScanPaths(cfg.Download.Dir, cfg.Organize.MoviesDir, cfg.Organize.TVShowsDir, cfg.Library.ScanPath)
scanPaths := daemonCfg.ScanPaths
if len(scanPaths) > 0 && cfg.Library.AutoScan {
scanInterval := 24 * time.Hour
if cfg.Library.ScanInterval != "" {