Merge pull request #17715 from Shadowghost/fix-people-cleanup

Delete credits nothing maps to and bound item-by-name folder names
This commit is contained in:
Cody Robibero
2026-08-25 18:28:52 -04:00
committed by GitHub
14 changed files with 231 additions and 25 deletions
@@ -194,12 +194,44 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
listOrder++;
}
var droppedCredits = existingMaps.Select(e => e.PeopleId).Distinct().ToArray();
context.PeopleBaseItemMap.RemoveRange(existingMaps);
context.SaveChanges();
// Nothing else ever deletes a credit row, so one left without a single mapping outlives the
// credit it stood for: it keeps a person of that name off the dead-person sweep, which only
// sees items no credit names, and keeps the name in every by-name list. That is how a credit
// a provider dropped, or one a broken provider result invented, becomes impossible to clean up.
DeleteCreditsWithoutMapping(context, droppedCredits);
context.SaveChanges();
transaction.Commit();
}
/// <inheritdoc/>
public int DeleteOrphanedCredits()
{
using var context = _dbProvider.CreateDbContext();
return DeleteCreditsWithoutMapping(context, null);
}
// A null candidate list sweeps every credit, anything else only the ones just unmapped.
private int DeleteCreditsWithoutMapping(JellyfinDbContext context, IReadOnlyList<Guid>? candidates)
{
if (candidates is not null && candidates.Count == 0)
{
return 0;
}
var credits = candidates is null
? context.Peoples.AsQueryable()
: context.Peoples.WhereOneOrMany(candidates, e => e.Id);
return credits.Where(e => !context.PeopleBaseItemMap.Any(f => f.PeopleId == e.Id)).ExecuteDelete();
}
/// <inheritdoc/>
public IReadOnlyDictionary<Guid, IReadOnlyList<string>> GetPeopleNamesByItems(IReadOnlyList<Guid> itemIds, IReadOnlyList<string> personTypes)
{