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:
parent
6955b6144b
commit
6adf1e2c4c
13 changed files with 1065 additions and 16 deletions
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/torrentclaw/unarr/internal/config"
|
||||
"github.com/torrentclaw/unarr/internal/engine"
|
||||
"github.com/torrentclaw/unarr/internal/library"
|
||||
"github.com/torrentclaw/unarr/internal/mediaserver"
|
||||
"github.com/torrentclaw/unarr/internal/usenet/download"
|
||||
)
|
||||
|
||||
|
|
@ -237,10 +238,28 @@ func runDaemonStart() error {
|
|||
// Trigger immediate sync when a download slot frees up
|
||||
manager.OnTaskDone = func() { d.TriggerSync() }
|
||||
|
||||
// Trigger Plex/Jellyfin/Emby library refresh after a task finalises so
|
||||
// the new file appears in the user's library within seconds (instead
|
||||
// of waiting for the next periodic scan). No-op if no servers
|
||||
// configured. Errors are logged inside Refresh and never propagate.
|
||||
if len(cfg.MediaServers) > 0 {
|
||||
manager.OnFinalized = func(task *engine.Task) {
|
||||
if task == nil {
|
||||
return
|
||||
}
|
||||
fp := task.SafeFilePath()
|
||||
if fp == "" {
|
||||
return
|
||||
}
|
||||
mediaserver.Refresh(cfg.MediaServers, fp)
|
||||
}
|
||||
}
|
||||
|
||||
// Wire: sync receives new tasks → submit to manager or handle stream
|
||||
d.OnTasksClaimed = func(tasks []agent.Task) {
|
||||
for _, t := range tasks {
|
||||
if t.Mode == "stream" {
|
||||
switch t.Mode {
|
||||
case "stream":
|
||||
if isStreamingTask(t.ID) {
|
||||
continue
|
||||
}
|
||||
|
|
@ -251,7 +270,9 @@ func runDaemonStart() error {
|
|||
streamRegistry.cancels[t.ID] = streamCancel
|
||||
streamRegistry.mu.Unlock()
|
||||
go handleStreamTask(streamCtx, t, reporter, cfg, agentClient, streamSrv)
|
||||
} else {
|
||||
case "strm-to-library":
|
||||
go handleStrmToLibrary(ctx, t, cfg, agentClient)
|
||||
default:
|
||||
manager.Submit(ctx, t)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue