mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-03 23:36:38 +01:00
Cleanup folder duplicates of series
This commit is contained in:
@@ -101,7 +101,9 @@ public class FixIncorrectOwnerIdRelationships : IAsyncMigrationRoutine
|
||||
{
|
||||
b.Id,
|
||||
b.Type,
|
||||
HasChildren = context.BaseItems.Any(c => c.OwnerId.HasValue && c.OwnerId.Value.Equals(b.Id) && c.ExtraType != null && c.ExtraType != 0)
|
||||
b.DateCreated,
|
||||
HasOwnedExtras = context.BaseItems.Any(c => c.OwnerId.HasValue && c.OwnerId.Value.Equals(b.Id)),
|
||||
HasDirectChildren = context.BaseItems.Any(c => c.ParentId.HasValue && c.ParentId.Value.Equals(b.Id))
|
||||
})
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -111,10 +113,13 @@ public class FixIncorrectOwnerIdRelationships : IAsyncMigrationRoutine
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep the item that has legitimate children (extras), then prefer Movie type over Video type, then lowest ID
|
||||
var itemWithChildren = itemsWithPath.FirstOrDefault(i => i.HasChildren);
|
||||
var movieTypeItem = itemsWithPath.FirstOrDefault(i => i.Type == "MediaBrowser.Controller.Entities.Movies.Movie");
|
||||
var itemToKeep = itemWithChildren ?? movieTypeItem ?? itemsWithPath.MinBy(i => i.Id);
|
||||
// Keep the item that has direct children, then owned extras, then prefer non-Folder types, then newest
|
||||
var itemToKeep = itemsWithPath
|
||||
.OrderByDescending(i => i.HasDirectChildren)
|
||||
.ThenByDescending(i => i.HasOwnedExtras)
|
||||
.ThenByDescending(i => i.Type != "MediaBrowser.Controller.Entities.Folder")
|
||||
.ThenByDescending(i => i.DateCreated)
|
||||
.First();
|
||||
if (itemToKeep is null)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -420,6 +420,17 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
// Create a list for our validated children
|
||||
var newItems = new List<BaseItem>();
|
||||
var actuallyRemoved = new List<BaseItem>();
|
||||
|
||||
// Build a reverse path→item lookup for detecting type changes
|
||||
var currentChildrenByPath = new Dictionary<string, BaseItem>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kvp in currentChildren)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(kvp.Value.Path))
|
||||
{
|
||||
currentChildrenByPath.TryAdd(kvp.Value.Path, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -447,6 +458,24 @@ namespace MediaBrowser.Controller.Entities
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if an existing item occupies the same path with different type/ID
|
||||
if (!string.IsNullOrEmpty(child.Path)
|
||||
&& currentChildrenByPath.TryGetValue(child.Path, out var staleItem)
|
||||
&& !staleItem.Id.Equals(child.Id))
|
||||
{
|
||||
Logger.LogInformation(
|
||||
"Item type changed at {Path}: {OldType} -> {NewType}, removing stale entry",
|
||||
child.Path,
|
||||
staleItem.GetType().Name,
|
||||
child.GetType().Name);
|
||||
|
||||
currentChildren.Remove(staleItem.Id);
|
||||
currentChildrenByPath.Remove(child.Path);
|
||||
staleItem.SetParent(null);
|
||||
LibraryManager.DeleteItem(staleItem, new DeleteOptions { DeleteFileLocation = false }, this, false);
|
||||
actuallyRemoved.Add(staleItem);
|
||||
}
|
||||
|
||||
// Brand new item - needs to be added
|
||||
child.SetParent(this);
|
||||
newItems.Add(child);
|
||||
@@ -456,7 +485,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
// That's all the new and changed ones - now see if any have been removed and need cleanup
|
||||
var itemsRemoved = currentChildren.Values.Except(validChildren).ToList();
|
||||
var shouldRemove = !IsRoot || allowRemoveRoot;
|
||||
var actuallyRemoved = new List<BaseItem>();
|
||||
// If it's an AggregateFolder, don't remove
|
||||
if (shouldRemove && itemsRemoved.Count > 0)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user