mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-06-01 04:34:26 -04:00
Fix Deduplication and Save of Items
This commit is contained in:
parent
fcb1dfc010
commit
7b81a39ee1
@ -1270,6 +1270,7 @@ public sealed class BaseItemRepository(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete();
|
||||||
context.BaseItems.Attach(entity).State = EntityState.Modified;
|
context.BaseItems.Attach(entity).State = EntityState.Modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1289,22 +1290,23 @@ public sealed class BaseItemRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags);
|
var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags);
|
||||||
var itemValues = itemValuesToSave.Select(e => e.Value).ToArray();
|
|
||||||
context.ItemValuesMap.Where(e => e.ItemId == entity.Id).ExecuteDelete();
|
context.ItemValuesMap.Where(e => e.ItemId == entity.Id).ExecuteDelete();
|
||||||
entity.ItemValues = new List<ItemValueMap>();
|
entity.ItemValues = new List<ItemValueMap>();
|
||||||
var referenceValues = context.ItemValues.Where(e => itemValues.Any(f => f == e.CleanValue)).ToArray();
|
|
||||||
|
|
||||||
foreach (var itemValue in itemValuesToSave)
|
foreach (var itemValue in itemValuesToSave)
|
||||||
{
|
{
|
||||||
var refValue = referenceValues.FirstOrDefault(f => f.CleanValue == itemValue.Value && (int)f.Type == itemValue.MagicNumber);
|
var refValue = context.ItemValues
|
||||||
if (refValue is not null)
|
.Where(f => f.CleanValue == GetCleanValue(itemValue.Value) && (int)f.Type == itemValue.MagicNumber)
|
||||||
|
.Select(e => e.ItemValueId)
|
||||||
|
.FirstOrDefault();
|
||||||
|
if (!refValue.IsEmpty())
|
||||||
{
|
{
|
||||||
entity.ItemValues.Add(new ItemValueMap()
|
entity.ItemValues.Add(new ItemValueMap()
|
||||||
{
|
{
|
||||||
Item = entity,
|
Item = entity,
|
||||||
ItemId = entity.Id,
|
ItemId = entity.Id,
|
||||||
ItemValue = null!,
|
ItemValue = null!,
|
||||||
ItemValueId = refValue.ItemValueId
|
ItemValueId = refValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
1595
Jellyfin.Server.Implementations/Migrations/20241113133548_EnforceUniqueItemValue.Designer.cs
generated
Normal file
1595
Jellyfin.Server.Implementations/Migrations/20241113133548_EnforceUniqueItemValue.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,37 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class EnforceUniqueItemValue : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_ItemValues_Type_CleanValue",
|
||||||
|
table: "ItemValues");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ItemValues_Type_CleanValue",
|
||||||
|
table: "ItemValues",
|
||||||
|
columns: new[] { "Type", "CleanValue" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_ItemValues_Type_CleanValue",
|
||||||
|
table: "ItemValues");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ItemValues_Type_CleanValue",
|
||||||
|
table: "ItemValues",
|
||||||
|
columns: new[] { "Type", "CleanValue" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -690,7 +690,8 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
|
|
||||||
b.HasKey("ItemValueId");
|
b.HasKey("ItemValueId");
|
||||||
|
|
||||||
b.HasIndex("Type", "CleanValue");
|
b.HasIndex("Type", "CleanValue")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("ItemValues");
|
b.ToTable("ItemValues");
|
||||||
});
|
});
|
||||||
|
@ -14,6 +14,6 @@ public class ItemValuesConfiguration : IEntityTypeConfiguration<ItemValue>
|
|||||||
public void Configure(EntityTypeBuilder<ItemValue> builder)
|
public void Configure(EntityTypeBuilder<ItemValue> builder)
|
||||||
{
|
{
|
||||||
builder.HasKey(e => e.ItemValueId);
|
builder.HasKey(e => e.ItemValueId);
|
||||||
builder.HasIndex(e => new { e.Type, e.CleanValue });
|
builder.HasIndex(e => new { e.Type, e.CleanValue }).IsUnique();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,45 @@ public class MigrateLibraryDb : IMigrationRoutine
|
|||||||
_logger.LogInformation("Saving BaseItems entries took {0}.", stopwatch.Elapsed);
|
_logger.LogInformation("Saving BaseItems entries took {0}.", stopwatch.Elapsed);
|
||||||
stopwatch.Restart();
|
stopwatch.Restart();
|
||||||
|
|
||||||
|
_logger.LogInformation("Start moving ItemValues.");
|
||||||
|
// do not migrate inherited types as they are now properly mapped in search and lookup.
|
||||||
|
var itemValueQuery = "select ItemId, Type, Value, CleanValue FROM ItemValues WHERE Type <> 6";
|
||||||
|
dbContext.ItemValues.ExecuteDelete();
|
||||||
|
|
||||||
|
// EFCores local lookup sucks.
|
||||||
|
var localItems = new Dictionary<(int Type, string CleanValue), (ItemValue ItemValue, List<Guid> ItemIds)>();
|
||||||
|
|
||||||
|
foreach (SqliteDataReader dto in connection.Query(itemValueQuery))
|
||||||
|
{
|
||||||
|
var itemId = dto.GetGuid(0);
|
||||||
|
var entity = GetItemValue(dto);
|
||||||
|
var key = ((int)entity.Type, entity.CleanValue);
|
||||||
|
if (!localItems.TryGetValue(key, out var existing))
|
||||||
|
{
|
||||||
|
localItems[key] = existing = (entity, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.ItemIds.Add(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in localItems)
|
||||||
|
{
|
||||||
|
dbContext.ItemValues.Add(item.Value.ItemValue);
|
||||||
|
dbContext.ItemValuesMap.AddRange(item.Value.ItemIds.Distinct().Select(f => new ItemValueMap()
|
||||||
|
{
|
||||||
|
Item = null!,
|
||||||
|
ItemValue = null!,
|
||||||
|
ItemId = f,
|
||||||
|
ItemValueId = item.Value.ItemValue.ItemValueId
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Try saving {0} ItemValues entries.", dbContext.ItemValues.Local.Count);
|
||||||
|
dbContext.SaveChanges();
|
||||||
|
migrationTotalTime += stopwatch.Elapsed;
|
||||||
|
_logger.LogInformation("Saving People ItemValues took {0}.", stopwatch.Elapsed);
|
||||||
|
stopwatch.Restart();
|
||||||
|
|
||||||
_logger.LogInformation("Start moving UserData.");
|
_logger.LogInformation("Start moving UserData.");
|
||||||
var queryResult = connection.Query("SELECT key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex FROM UserDatas");
|
var queryResult = connection.Query("SELECT key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex FROM UserDatas");
|
||||||
|
|
||||||
@ -158,6 +197,8 @@ public class MigrateLibraryDb : IMigrationRoutine
|
|||||||
dbContext.Peoples.ExecuteDelete();
|
dbContext.Peoples.ExecuteDelete();
|
||||||
dbContext.PeopleBaseItemMap.ExecuteDelete();
|
dbContext.PeopleBaseItemMap.ExecuteDelete();
|
||||||
|
|
||||||
|
var peopleCache = new Dictionary<string, (People Person, List<PeopleBaseItemMap> Items)>();
|
||||||
|
|
||||||
foreach (SqliteDataReader reader in connection.Query(personsQuery))
|
foreach (SqliteDataReader reader in connection.Query(personsQuery))
|
||||||
{
|
{
|
||||||
var itemId = reader.GetGuid(0);
|
var itemId = reader.GetGuid(0);
|
||||||
@ -168,11 +209,9 @@ public class MigrateLibraryDb : IMigrationRoutine
|
|||||||
}
|
}
|
||||||
|
|
||||||
var entity = GetPerson(reader);
|
var entity = GetPerson(reader);
|
||||||
var existingPerson = dbContext.Peoples.FirstOrDefault(e => e.Name == entity.Name);
|
if (!peopleCache.TryGetValue(entity.Name, out var personCache))
|
||||||
if (existingPerson is null)
|
|
||||||
{
|
{
|
||||||
dbContext.Peoples.Add(entity);
|
peopleCache[entity.Name] = personCache = (entity, []);
|
||||||
existingPerson = entity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader.TryGetString(2, out var role))
|
if (reader.TryGetString(2, out var role))
|
||||||
@ -183,58 +222,30 @@ public class MigrateLibraryDb : IMigrationRoutine
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
dbContext.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
|
personCache.Items.Add(new PeopleBaseItemMap()
|
||||||
{
|
{
|
||||||
Item = null!,
|
Item = null!,
|
||||||
ItemId = itemId,
|
ItemId = itemId,
|
||||||
People = existingPerson,
|
People = null!,
|
||||||
PeopleId = existingPerson.Id,
|
PeopleId = personCache.Person.Id,
|
||||||
ListOrder = sortOrder,
|
ListOrder = sortOrder,
|
||||||
SortOrder = sortOrder,
|
SortOrder = sortOrder,
|
||||||
Role = role
|
Role = role
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var item in peopleCache)
|
||||||
|
{
|
||||||
|
dbContext.Peoples.Add(item.Value.Person);
|
||||||
|
dbContext.PeopleBaseItemMap.AddRange(item.Value.Items.DistinctBy(e => (e.ItemId, e.PeopleId)));
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Try saving {0} People entries.", dbContext.MediaStreamInfos.Local.Count);
|
_logger.LogInformation("Try saving {0} People entries.", dbContext.MediaStreamInfos.Local.Count);
|
||||||
dbContext.SaveChanges();
|
dbContext.SaveChanges();
|
||||||
migrationTotalTime += stopwatch.Elapsed;
|
migrationTotalTime += stopwatch.Elapsed;
|
||||||
_logger.LogInformation("Saving People entries took {0}.", stopwatch.Elapsed);
|
_logger.LogInformation("Saving People entries took {0}.", stopwatch.Elapsed);
|
||||||
stopwatch.Restart();
|
stopwatch.Restart();
|
||||||
|
|
||||||
_logger.LogInformation("Start moving ItemValues.");
|
|
||||||
// do not migrate inherited types as they are now properly mapped in search and lookup.
|
|
||||||
var itemValueQuery = "select ItemId, Type, Value, CleanValue FROM ItemValues WHERE Type <> 6";
|
|
||||||
dbContext.ItemValues.ExecuteDelete();
|
|
||||||
|
|
||||||
foreach (SqliteDataReader dto in connection.Query(itemValueQuery))
|
|
||||||
{
|
|
||||||
var itemId = dto.GetGuid(0);
|
|
||||||
var entity = GetItemValue(dto);
|
|
||||||
var existingItemValue = dbContext.ItemValues.FirstOrDefault(f => f.Type == entity.Type && f.Value == entity.Value);
|
|
||||||
if (existingItemValue is null)
|
|
||||||
{
|
|
||||||
dbContext.ItemValues.Add(entity);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
entity = existingItemValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
dbContext.ItemValuesMap.Add(new ItemValueMap()
|
|
||||||
{
|
|
||||||
Item = null!,
|
|
||||||
ItemValue = null!,
|
|
||||||
ItemId = itemId,
|
|
||||||
ItemValueId = entity.ItemValueId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Try saving {0} ItemValues entries.", dbContext.ItemValues.Local.Count);
|
|
||||||
dbContext.SaveChanges();
|
|
||||||
migrationTotalTime += stopwatch.Elapsed;
|
|
||||||
_logger.LogInformation("Saving People ItemValues took {0}.", stopwatch.Elapsed);
|
|
||||||
stopwatch.Restart();
|
|
||||||
|
|
||||||
_logger.LogInformation("Start moving Chapters.");
|
_logger.LogInformation("Start moving Chapters.");
|
||||||
var chapterQuery = "select ItemId,StartPositionTicks,Name,ImagePath,ImageDateModified,ChapterIndex from Chapters2";
|
var chapterQuery = "select ItemId,StartPositionTicks,Name,ImagePath,ImageDateModified,ChapterIndex from Chapters2";
|
||||||
dbContext.Chapters.ExecuteDelete();
|
dbContext.Chapters.ExecuteDelete();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user