feat(mediaserver): Plex/Jellyfin/Emby auto-refresh + .strm instant mode

Sprint 1 — Auto-refresh after download:
- New [[mediaserver]] TOML section with kind/url/token/sections
- mediaserver.Refresh() fans out to Plex (partial via section ID auto-mapping
  from file path prefix) and Jellyfin/Emby (full library scan)
- Manager.OnFinalized callback wired in daemon to trigger refresh after
  organize() completes — keeps engine package free of mediaserver dep
- New unarr mediaserver {setup,list,remove,test} commands
- unarr init wizard offers to configure refresh when a server is detected

Sprint 2 — .strm instant mode (cloud + agent):
- Mode strm-to-library handled in daemon dispatch: writes a one-line .strm
  file pointing to the cloud-resolved debrid HTTPS URL, then triggers refresh
- engine.WriteStrm + StrmDestForTask mirror organize()'s naming so Plex/Jellyfin
  see the expected folder structure (Movies/Title (Year)/, TV Shows/Show/Season XX/)
- Atomic write (temp + rename) so partial files never get indexed
- Reports completed/failed status to the cloud via existing agent client
This commit is contained in:
Deivid Soto 2026-05-05 20:35:08 +02:00
parent 6955b6144b
commit 6adf1e2c4c
13 changed files with 1065 additions and 16 deletions

View file

@ -33,6 +33,12 @@ type Manager struct {
// Used by the daemon to trigger an immediate sync.
OnTaskDone func()
// OnFinalized is called after a task successfully transitions to
// completed (after verify + organize + optional upgrade replace).
// Used by the daemon to trigger media-server library refreshes.
// Not invoked on failure.
OnFinalized func(task *Task)
// recentlyFinished holds tasks that completed/failed since the last sync read.
// The sync goroutine reads and clears this to include final states in the next sync.
recentMu sync.Mutex
@ -444,6 +450,9 @@ func (m *Manager) finalize(ctx context.Context, task *Task, result *Result) {
if m.cfg.Notifications {
desktopNotify("Download complete", task.Title)
}
if m.OnFinalized != nil {
m.OnFinalized(task)
}
m.recordFinished(task.ToStatusUpdate())
m.reporter.ReportFinal(ctx, task)
}