Add JSON config, DB-backed library options, and docs
- Add JSON-based config loading with XML fallback for DB and library options - Implement LibraryOptionsRepository with EF Core, migrations, and entity - Update CollectionFolder to use DB-backed options with XML fallback/backfill - Register repository in DI and initialize at startup - Use EF execution strategy for transactional DB operations - Suppress code analysis warnings in .csproj and test files - Add DATABASE_MIGRATION.md, LIBRARY_OPTIONS_DB_DESIGN.md, and WEBSOCKET_AUTHENTICATION.md - Add database.json.example and improve migration docs - Add tests for JSON config loader and update test naming warnings
This commit is contained in:
@@ -123,77 +123,81 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
var personKeys = people.Select(e => e.Name + "-" + e.Type).ToArray();
|
||||
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var existingPersons = await context.Peoples
|
||||
.Select(e => new
|
||||
{
|
||||
item = e,
|
||||
SelectionKey = e.Name + "-" + e.PersonType
|
||||
})
|
||||
.Where(p => personKeys.Contains(p.SelectionKey))
|
||||
.Select(f => f.item)
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var toAdd = people
|
||||
.Where(e => e.Type is not PersonKind.Artist && e.Type is not PersonKind.AlbumArtist)
|
||||
.Where(e => !existingPersons.Any(f => f.Name == e.Name && f.PersonType == e.Type.ToString()))
|
||||
.Select(Map);
|
||||
|
||||
await context.Peoples.AddRangeAsync(toAdd, cancellationToken).ConfigureAwait(false);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var personsEntities = toAdd.Concat(existingPersons).ToArray();
|
||||
|
||||
var existingMaps = await context.PeopleBaseItemMap
|
||||
.Include(e => e.People)
|
||||
.Where(e => e.ItemId == itemId)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var listOrder = 0;
|
||||
|
||||
foreach (var person in people)
|
||||
// Use the execution strategy to handle transactional consistency and retries
|
||||
// This is required for PostgreSQL NpgsqlRetryingExecutionStrategy
|
||||
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||
await executionStrategy.ExecuteAsync(async () =>
|
||||
{
|
||||
if (person.Type == PersonKind.Artist || person.Type == PersonKind.AlbumArtist)
|
||||
var existingPersons = await context.Peoples
|
||||
.Select(e => new
|
||||
{
|
||||
item = e,
|
||||
SelectionKey = e.Name + "-" + e.PersonType
|
||||
})
|
||||
.Where(p => personKeys.Contains(p.SelectionKey))
|
||||
.Select(f => f.item)
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var toAdd = people
|
||||
.Where(e => e.Type is not PersonKind.Artist && e.Type is not PersonKind.AlbumArtist)
|
||||
.Where(e => !existingPersons.Any(f => f.Name == e.Name && f.PersonType == e.Type.ToString()))
|
||||
.Select(Map);
|
||||
|
||||
await context.Peoples.AddRangeAsync(toAdd, cancellationToken).ConfigureAwait(false);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var personsEntities = toAdd.Concat(existingPersons).ToArray();
|
||||
|
||||
var existingMaps = await context.PeopleBaseItemMap
|
||||
.Include(e => e.People)
|
||||
.Where(e => e.ItemId == itemId)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var listOrder = 0;
|
||||
|
||||
foreach (var person in people)
|
||||
{
|
||||
continue;
|
||||
if (person.Type == PersonKind.Artist || person.Type == PersonKind.AlbumArtist)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var entityPerson = personsEntities.First(e => e.Name == person.Name && e.PersonType == person.Type.ToString());
|
||||
var existingMap = existingMaps.FirstOrDefault(e => e.People.Name == person.Name && e.People.PersonType == person.Type.ToString() && e.Role == person.Role);
|
||||
if (existingMap is null)
|
||||
{
|
||||
await context.PeopleBaseItemMap.AddAsync(
|
||||
new PeopleBaseItemMap()
|
||||
{
|
||||
Item = null!,
|
||||
ItemId = itemId,
|
||||
People = null!,
|
||||
PeopleId = entityPerson.Id,
|
||||
ListOrder = listOrder,
|
||||
SortOrder = person.SortOrder,
|
||||
Role = person.Role
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the order for existing mappings
|
||||
existingMap.ListOrder = listOrder;
|
||||
existingMap.SortOrder = person.SortOrder;
|
||||
// person mapping already exists so remove from list
|
||||
existingMaps.Remove(existingMap);
|
||||
}
|
||||
|
||||
listOrder++;
|
||||
}
|
||||
|
||||
var entityPerson = personsEntities.First(e => e.Name == person.Name && e.PersonType == person.Type.ToString());
|
||||
var existingMap = existingMaps.FirstOrDefault(e => e.People.Name == person.Name && e.People.PersonType == person.Type.ToString() && e.Role == person.Role);
|
||||
if (existingMap is null)
|
||||
{
|
||||
await context.PeopleBaseItemMap.AddAsync(
|
||||
new PeopleBaseItemMap()
|
||||
{
|
||||
Item = null!,
|
||||
ItemId = itemId,
|
||||
People = null!,
|
||||
PeopleId = entityPerson.Id,
|
||||
ListOrder = listOrder,
|
||||
SortOrder = person.SortOrder,
|
||||
Role = person.Role
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the order for existing mappings
|
||||
existingMap.ListOrder = listOrder;
|
||||
existingMap.SortOrder = person.SortOrder;
|
||||
// person mapping already exists so remove from list
|
||||
existingMaps.Remove(existingMap);
|
||||
}
|
||||
context.PeopleBaseItemMap.RemoveRange(existingMaps);
|
||||
|
||||
listOrder++;
|
||||
}
|
||||
|
||||
context.PeopleBaseItemMap.RemoveRange(existingMaps);
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private PersonInfo Map(People people)
|
||||
|
||||
Reference in New Issue
Block a user