fix duplicate smart collection names (#2720)
* fix duplicate smart collection names * fix update error
This commit is contained in:
@@ -56,6 +56,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- NVIDIA: fix stream failure with certain content that should decode in hardware but falls back to software
|
||||
- Fix stream failure when configured fallback filler collection is empty
|
||||
- Fix high CPU when errors are displayed; errors will now work ahead before throttling to realtime, similar to primary content
|
||||
- Fix startup error caused by duplicate smart collection names (and no longer allow duplicate smart collection names)
|
||||
|
||||
### Changed
|
||||
- No longer round framerate to nearest integer when normalizing framerate
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
<PropertyGroup>
|
||||
<InformationalVersion>develop</InformationalVersion>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<AllowMissingPrunePackageData>true</AllowMissingPrunePackageData>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -12,7 +12,9 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections;
|
||||
|
||||
public class UpdateSmartCollectionHandler : IRequestHandler<UpdateSmartCollection, Either<BaseError, UpdateSmartCollectionResult>>
|
||||
public class
|
||||
UpdateSmartCollectionHandler : IRequestHandler<UpdateSmartCollection,
|
||||
Either<BaseError, UpdateSmartCollectionResult>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
@@ -71,7 +73,8 @@ public class UpdateSmartCollectionHandler : IRequestHandler<UpdateSmartCollectio
|
||||
private static Task<Validation<BaseError, SmartCollection>> Validate(
|
||||
TvContext dbContext,
|
||||
UpdateSmartCollection request,
|
||||
CancellationToken cancellationToken) => SmartCollectionMustExist(dbContext, request, cancellationToken);
|
||||
CancellationToken cancellationToken) => ValidateName(dbContext, request)
|
||||
.BindT(_ => SmartCollectionMustExist(dbContext, request, cancellationToken));
|
||||
|
||||
private static Task<Validation<BaseError, SmartCollection>> SmartCollectionMustExist(
|
||||
TvContext dbContext,
|
||||
@@ -80,4 +83,23 @@ public class UpdateSmartCollectionHandler : IRequestHandler<UpdateSmartCollectio
|
||||
dbContext.SmartCollections
|
||||
.SelectOneAsync(c => c.Id, c => c.Id == updateCollection.Id, cancellationToken)
|
||||
.Map(o => o.ToValidation<BaseError>("SmartCollection does not exist."));
|
||||
|
||||
private static async Task<Validation<BaseError, string>> ValidateName(
|
||||
TvContext dbContext,
|
||||
UpdateSmartCollection updateCollection)
|
||||
{
|
||||
List<string> allNames = await dbContext.SmartCollections
|
||||
.Where(c => c.Id != updateCollection.Id)
|
||||
.Map(c => c.Name)
|
||||
.ToListAsync();
|
||||
|
||||
Validation<BaseError, string> result1 = updateCollection.NotEmpty(c => c.Name)
|
||||
.Bind(_ => updateCollection.NotLongerThan(50)(c => c.Name));
|
||||
|
||||
var result2 = Optional(updateCollection.Name)
|
||||
.Where(name => !allNames.Contains(name))
|
||||
.ToValidation<BaseError>("SmartCollection name must be unique");
|
||||
|
||||
return (result1, result2).Apply((_, _) => updateCollection.Name);
|
||||
}
|
||||
}
|
||||
|
||||
6882
ErsatzTV.Infrastructure.MySql/Migrations/20251213202531_Fix_DuplicateSmartCollectionName.Designer.cs
generated
Normal file
6882
ErsatzTV.Infrastructure.MySql/Migrations/20251213202531_Fix_DuplicateSmartCollectionName.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Fix_DuplicateSmartCollectionName : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
@"
|
||||
WITH Numbered AS (
|
||||
SELECT
|
||||
Id,
|
||||
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Id) as RowNum
|
||||
FROM SmartCollection
|
||||
)
|
||||
UPDATE SmartCollection sc
|
||||
JOIN Numbered n ON sc.Id = n.Id
|
||||
SET sc.Name = CONCAT(sc.Name, ' (', n.RowNum - 1, ')')
|
||||
WHERE n.RowNum > 1;
|
||||
");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
6885
ErsatzTV.Infrastructure.MySql/Migrations/20251213203336_Add_SmartCollectionNameUnique.Designer.cs
generated
Normal file
6885
ErsatzTV.Infrastructure.MySql/Migrations/20251213203336_Add_SmartCollectionNameUnique.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_SmartCollectionNameUnique : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "SmartCollection",
|
||||
type: "varchar(255)",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SmartCollection_Name",
|
||||
table: "SmartCollection",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_SmartCollection_Name",
|
||||
table: "SmartCollection");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "SmartCollection",
|
||||
type: "longtext",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(255)",
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3235,13 +3235,16 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("longtext");
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<string>("Query")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SmartCollection", (string)null);
|
||||
});
|
||||
|
||||
|
||||
6709
ErsatzTV.Infrastructure.Sqlite/Migrations/20251213202449_Fix_DuplicateSmartCollectionName.Designer.cs
generated
Normal file
6709
ErsatzTV.Infrastructure.Sqlite/Migrations/20251213202449_Fix_DuplicateSmartCollectionName.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Fix_DuplicateSmartCollectionName : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
@"
|
||||
WITH Numbered AS (
|
||||
SELECT
|
||||
Id,
|
||||
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Id) AS RowNum
|
||||
FROM SmartCollection
|
||||
)
|
||||
UPDATE SmartCollection
|
||||
SET Name = Name || ' (' || (Numbered.RowNum - 1) || ')'
|
||||
FROM Numbered
|
||||
WHERE SmartCollection.Id = Numbered.Id
|
||||
AND Numbered.RowNum > 1;
|
||||
");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
6712
ErsatzTV.Infrastructure.Sqlite/Migrations/20251213203256_Add_SmartCollectionNameUnique.Designer.cs
generated
Normal file
6712
ErsatzTV.Infrastructure.Sqlite/Migrations/20251213203256_Add_SmartCollectionNameUnique.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_SmartCollectionNameUnique : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SmartCollection_Name",
|
||||
table: "SmartCollection",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_SmartCollection_Name",
|
||||
table: "SmartCollection");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3091,6 +3091,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SmartCollection", (string)null);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,5 +6,11 @@ namespace ErsatzTV.Infrastructure.Data.Configurations;
|
||||
|
||||
public class SmartCollectionConfiguration : IEntityTypeConfiguration<SmartCollection>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<SmartCollection> builder) => builder.ToTable("SmartCollection");
|
||||
public void Configure(EntityTypeBuilder<SmartCollection> builder)
|
||||
{
|
||||
builder.ToTable("SmartCollection");
|
||||
|
||||
builder.HasIndex(sc => sc.Name)
|
||||
.IsUnique();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user