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

@ -87,6 +87,17 @@ func Detect() DetectedPaths {
// ── Plex ────────────────────────────────────────────────────────────
// LocalPlexToken returns the Plex auth token from the local Plex config
// directory, if Plex Media Server is installed on this host. Returns ""
// when Plex isn't installed or the token can't be read.
func LocalPlexToken() string {
dir := plexConfigDir()
if dir == "" {
return ""
}
return PlexTokenFromPrefs(filepath.Join(dir, "Preferences.xml"))
}
func plexLibraryPaths() []string {
configDir := plexConfigDir()
if configDir == "" {
@ -95,7 +106,7 @@ func plexLibraryPaths() []string {
// Read token from Preferences.xml
prefsPath := filepath.Join(configDir, "Preferences.xml")
token := plexTokenFromPrefs(prefsPath)
token := PlexTokenFromPrefs(prefsPath)
if token == "" {
return nil
}
@ -154,7 +165,10 @@ type plexPrefs struct {
PlexOnlineToken string `xml:"PlexOnlineToken,attr"`
}
func plexTokenFromPrefs(path string) string {
// PlexTokenFromPrefs reads the Plex auth token from a Preferences.xml file.
// Returns "" if the file can't be read or parsed. Used by the setup wizard
// when configuring a Plex server running on the same host as unarr.
func PlexTokenFromPrefs(path string) string {
data, err := os.ReadFile(path)
if err != nil {
return ""