fix: stop the season rebuild from rewriting users' .nfo files
The rebuild task saved each episode with ItemUpdateType.MetadataEdit. ItemUpdateType is a [Flags] enum whose highest value is MetadataEdit, so ProviderManager's "updateType >= MetadataEdit" test matched, and that branch exists precisely to save metadata locally "even if save local is off, if the metadata file already exists". EpisodeNfoSaver writes to Path.ChangeExtension(item.Path, ".nfo") — beside the media. The damage was worse than a stray write. The save happens while ParentIndexNumber is deliberately null, and the saver only emits <season> when the value is present, so an existing .nfo would have been rewritten with its season element removed and left stale after the refresh reassigned the value. MetadataDownload sits below the manual-edit threshold and keeps the savers out of it. Persistence is unchanged: UpdateItemsAsync writes to the database regardless of the reason given. Verified against the real failure mode rather than only in tests. The test library had no .nfo files, so the bug was unobservable; a hand-written .nfo containing <season>1</season> was placed next to an episode and the task run in full. The file was byte-identical afterwards. That experiment also surfaced separate, pre-existing behaviour worth documenting: an .nfo containing <season> is a local metadata source that outranks this plugin, so those episodes ignore the rebuild entirely until the file is removed. Recorded in the release notes with the available remedies. Also splits the cancellation test, which only ever proved the loop checks its token on entry, and adds a mid-run case. Both new tests are mutation-verified. Note: AI Generated Commit
This commit is contained in:
parent
bd35ae94b1
commit
c4f22a8553
|
|
@ -41,6 +41,17 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
|||
/// There is no default trigger. Regrouping a library is disruptive and must be an explicit
|
||||
/// choice rather than a side effect of saving the settings page.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Episodes are processed one at a time. Each refresh reaches TubeArchivist, so running them
|
||||
/// concurrently would multiply the load on it and interleave badly with a scheduled library
|
||||
/// scan. The cost is duration: expect roughly a second per episode.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// On a library large enough to run longer than <c>PlaylistCache</c>'s lifetime, the cache can
|
||||
/// refresh part way through. If playlists changed in TubeArchivist during the run, episodes
|
||||
/// handled before and after that point can be grouped against different playlist data. Running
|
||||
/// the task again once TubeArchivist has settled resolves it.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class RebuildPlaylistSeasonsTask : IScheduledTask
|
||||
{
|
||||
|
|
@ -128,10 +139,19 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
|||
|
||||
// Calling the injected manager rather than BaseItem.UpdateToRepositoryAsync,
|
||||
// which resolves the parent through the static BaseItem.LibraryManager.
|
||||
//
|
||||
// MetadataDownload, never MetadataEdit. ItemUpdateType is a [Flags] enum
|
||||
// whose highest value is MetadataEdit, and ProviderManager treats
|
||||
// "updateType >= MetadataEdit" as a manual edit: it then rewrites an .nfo
|
||||
// beside the media whenever one already exists, even with local metadata
|
||||
// saving switched off. That write would happen while ParentIndexNumber is
|
||||
// still null, stripping <season> from the user's file. Anything below
|
||||
// MetadataEdit skips the savers, and persistence is unaffected either way
|
||||
// because UpdateItemsAsync saves to the database regardless of the reason.
|
||||
await _libraryManager.UpdateItemAsync(
|
||||
episode,
|
||||
episode.GetParent(),
|
||||
ItemUpdateType.MetadataEdit,
|
||||
ItemUpdateType.MetadataDownload,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
cleared++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,18 @@ seasons left behind. Jellyfin removes the now-empty old seasons by itself.
|
|||
Season *names* may briefly read "Season Unknown" immediately afterwards. That is a cached field on
|
||||
the episode and the next library refresh restores the correct name.
|
||||
|
||||
**If you keep `.nfo` files next to your media, they win.** An `.nfo` containing `<season>` is a local
|
||||
metadata source, and Jellyfin prefers it over anything this plugin supplies, so those episodes stay
|
||||
where the file says regardless of the setting or the task. Measured: an episode with
|
||||
`<season>1</season>` in its `.nfo` ignored the rebuild entirely, then moved correctly the moment the
|
||||
file was removed. Either delete the `.nfo` files, remove their `<season>` element, or disable the
|
||||
"Nfo" metadata reader for the library.
|
||||
|
||||
The task itself will **not** modify or create `.nfo` files. It saves below Jellyfin's "manual edit"
|
||||
threshold precisely so the NFO savers stay out of the way — otherwise Jellyfin would rewrite any
|
||||
existing `.nfo` while the season number was cleared, silently stripping `<season>` from it. Verified
|
||||
against a hand-written `.nfo`: byte-identical after a full run.
|
||||
|
||||
> **Deleting the seasons by hand does not work** — worth stating, because it is the obvious approach.
|
||||
> Jellyfin derives a season's item id deterministically from its series and index number, and
|
||||
> deleting a season does not clear its episodes' stored season number, so a rescan recreates exactly
|
||||
|
|
|
|||
Loading…
Reference in New Issue