Browse Source

fix app startup with mysql (#2205)

* don't run pragma command on mysql

* add not required pathhash

* make media file path hash required

* update changelog
pull/2206/head
Jason Dove 3 weeks ago committed by GitHub
parent
commit
ab0f431c85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 4
      ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs
  3. 1
      ErsatzTV.Core/Domain/MediaItem/MediaFile.cs
  4. 19
      ErsatzTV.Core/PathUtils.cs
  5. 10
      ErsatzTV.Infrastructure.MySql/Migrations/20250726155754_Add_MediaFilePathHash.Designer.cs
  6. 23
      ErsatzTV.Infrastructure.MySql/Migrations/20250726155754_Add_MediaFilePathHash.cs
  7. 6145
      ErsatzTV.Infrastructure.MySql/Migrations/20250726171512_Update_MediaFilePathHash.Designer.cs
  8. 56
      ErsatzTV.Infrastructure.MySql/Migrations/20250726171512_Update_MediaFilePathHash.cs
  9. 6
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  10. 5978
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250726160311_Add_MediaFilePathHash.Designer.cs
  11. 38
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250726160311_Add_MediaFilePathHash.cs
  12. 5982
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250726171343_Update_MediaFilePathHash.Designer.cs
  13. 46
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250726171343_Update_MediaFilePathHash.cs
  14. 6
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  15. 5
      ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaFileConfiguration.cs
  16. 5
      ErsatzTV.Infrastructure/Data/DbInitializer.cs
  17. 2
      ErsatzTV.Infrastructure/Data/Repositories/ImageRepository.cs
  18. 2
      ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs
  19. 2
      ErsatzTV.Infrastructure/Data/Repositories/MusicVideoRepository.cs
  20. 2
      ErsatzTV.Infrastructure/Data/Repositories/OtherVideoRepository.cs
  21. 2
      ErsatzTV.Infrastructure/Data/Repositories/RemoteStreamRepository.cs
  22. 2
      ErsatzTV.Infrastructure/Data/Repositories/SongRepository.cs
  23. 2
      ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs
  24. 1
      ErsatzTV.Infrastructure/Data/TvContext.cs
  25. 32
      ErsatzTV/Services/RunOnce/DatabaseMigratorService.cs
  26. 4
      ErsatzTV/Startup.cs

2
CHANGELOG.md

@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Fixed
- Fix app startup with MySql/MariaDB
## [25.3.1] - 2025-07-24
### Fixed

4
ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs

@ -136,7 +136,9 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI @@ -136,7 +136,9 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
{
if (key.StartsWith("ETV_", StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("DOTNET_", StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("ASPNETCORE_", StringComparison.OrdinalIgnoreCase))
|| key.StartsWith("ASPNETCORE_", StringComparison.OrdinalIgnoreCase)
|| key.Equals("PROVIDER", StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("ELASTICSEARCH", StringComparison.OrdinalIgnoreCase))
{
environment[key] = value;
}

1
ErsatzTV.Core/Domain/MediaItem/MediaFile.cs

@ -4,6 +4,7 @@ public class MediaFile @@ -4,6 +4,7 @@ public class MediaFile
{
public int Id { get; set; }
public string Path { get; set; }
public string PathHash { get; set; }
public int MediaVersionId { get; set; }
public MediaVersion MediaVersion { get; set; }

19
ErsatzTV.Core/PathUtils.cs

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
namespace ErsatzTV.Core;
public static class PathUtils
{
public static string GetPathHash(string path)
{
byte[] bytes = SHA256.HashData(Encoding.UTF8.GetBytes(path));
var builder = new StringBuilder();
foreach (byte b in bytes)
{
builder.Append(b.ToString("x2", CultureInfo.InvariantCulture));
}
return builder.ToString();
}
}

10
ErsatzTV.Infrastructure.MySql/Migrations/20250723030616_Update_MediaFilePath.Designer.cs → ErsatzTV.Infrastructure.MySql/Migrations/20250726155754_Add_MediaFilePathHash.Designer.cs generated

@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
[DbContext(typeof(TvContext))]
[Migration("20250723030616_Update_MediaFilePath")]
partial class Update_MediaFilePath
[Migration("20250726155754_Add_MediaFilePathHash")]
partial class Add_MediaFilePathHash
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -1116,15 +1116,15 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -1116,15 +1116,15 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<string>("Path")
.HasColumnType("longtext");
b.Property<string>("PathHash")
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("LibraryFolderId");
b.HasIndex("MediaVersionId");
b.HasIndex("Path")
.IsUnique();
b.ToTable("MediaFile", (string)null);
b.UseTptMappingStrategy();

23
ErsatzTV.Infrastructure.MySql/Migrations/20250723030616_Update_MediaFilePath.cs → ErsatzTV.Infrastructure.MySql/Migrations/20250726155754_Add_MediaFilePathHash.cs

@ -5,11 +5,15 @@ @@ -5,11 +5,15 @@
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Update_MediaFilePath : Migration
public partial class Add_MediaFilePathHash : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_MediaFile_Path",
table: "MediaFile");
migrationBuilder.AlterColumn<string>(
name: "Path",
table: "MediaFile",
@ -20,11 +24,22 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -20,11 +24,22 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<string>(
name: "PathHash",
table: "MediaFile",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PathHash",
table: "MediaFile");
migrationBuilder.AlterColumn<string>(
name: "Path",
table: "MediaFile",
@ -35,6 +50,12 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -35,6 +50,12 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_MediaFile_Path",
table: "MediaFile",
column: "Path",
unique: true);
}
}
}

6145
ErsatzTV.Infrastructure.MySql/Migrations/20250726171512_Update_MediaFilePathHash.Designer.cs generated

File diff suppressed because it is too large Load Diff

56
ErsatzTV.Infrastructure.MySql/Migrations/20250726171512_Update_MediaFilePathHash.cs

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Update_MediaFilePathHash : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "MediaFile",
keyColumn: "PathHash",
keyValue: null,
column: "PathHash",
value: "");
migrationBuilder.AlterColumn<string>(
name: "PathHash",
table: "MediaFile",
type: "varchar(255)",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_MediaFile_PathHash",
table: "MediaFile",
column: "PathHash",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_MediaFile_PathHash",
table: "MediaFile");
migrationBuilder.AlterColumn<string>(
name: "PathHash",
table: "MediaFile",
type: "longtext",
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(255)")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}

6
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -1113,13 +1113,17 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -1113,13 +1113,17 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<string>("Path")
.HasColumnType("longtext");
b.Property<string>("PathHash")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("LibraryFolderId");
b.HasIndex("MediaVersionId");
b.HasIndex("Path")
b.HasIndex("PathHash")
.IsUnique();
b.ToTable("MediaFile", (string)null);

5978
ErsatzTV.Infrastructure.Sqlite/Migrations/20250726160311_Add_MediaFilePathHash.Designer.cs generated

File diff suppressed because it is too large Load Diff

38
ErsatzTV.Infrastructure.Sqlite/Migrations/20250726160311_Add_MediaFilePathHash.cs

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_MediaFilePathHash : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_MediaFile_Path",
table: "MediaFile");
migrationBuilder.AddColumn<string>(
name: "PathHash",
table: "MediaFile",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PathHash",
table: "MediaFile");
migrationBuilder.CreateIndex(
name: "IX_MediaFile_Path",
table: "MediaFile",
column: "Path",
unique: true);
}
}
}

5982
ErsatzTV.Infrastructure.Sqlite/Migrations/20250726171343_Update_MediaFilePathHash.Designer.cs generated

File diff suppressed because it is too large Load Diff

46
ErsatzTV.Infrastructure.Sqlite/Migrations/20250726171343_Update_MediaFilePathHash.cs

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Update_MediaFilePathHash : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "PathHash",
table: "MediaFile",
type: "TEXT",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.CreateIndex(
name: "IX_MediaFile_PathHash",
table: "MediaFile",
column: "PathHash",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_MediaFile_PathHash",
table: "MediaFile");
migrationBuilder.AlterColumn<string>(
name: "PathHash",
table: "MediaFile",
type: "TEXT",
nullable: true,
oldClrType: typeof(string),
oldType: "TEXT");
}
}
}

6
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -1056,13 +1056,17 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -1056,13 +1056,17 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<string>("Path")
.HasColumnType("TEXT");
b.Property<string>("PathHash")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LibraryFolderId");
b.HasIndex("MediaVersionId");
b.HasIndex("Path")
b.HasIndex("PathHash")
.IsUnique();
b.ToTable("MediaFile", (string)null);

5
ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaFileConfiguration.cs

@ -10,9 +10,12 @@ public class MediaFileConfiguration : IEntityTypeConfiguration<MediaFile> @@ -10,9 +10,12 @@ public class MediaFileConfiguration : IEntityTypeConfiguration<MediaFile>
{
builder.ToTable("MediaFile");
builder.HasIndex(f => f.Path)
builder.HasIndex(f => f.PathHash)
.IsUnique();
builder.Property(f => f.PathHash)
.IsRequired();
builder.HasOne(f => f.LibraryFolder)
.WithMany(f => f.MediaFiles)
.HasForeignKey(f => f.LibraryFolderId)

5
ErsatzTV.Infrastructure/Data/DbInitializer.cs

@ -9,7 +9,10 @@ public static class DbInitializer @@ -9,7 +9,10 @@ public static class DbInitializer
{
public static async Task<Unit> Initialize(TvContext context, CancellationToken cancellationToken)
{
await context.Connection.ExecuteAsync("PRAGMA journal_mode=WAL", cancellationToken);
if (TvContext.IsSqlite)
{
await context.Connection.ExecuteAsync("PRAGMA journal_mode=WAL", cancellationToken);
}
if (!context.LanguageCodes.Any())
{

2
ErsatzTV.Infrastructure/Data/Repositories/ImageRepository.cs

@ -142,7 +142,7 @@ public class ImageRepository : IImageRepository @@ -142,7 +142,7 @@ public class ImageRepository : IImageRepository
[
new MediaVersion
{
MediaFiles = [new MediaFile { Path = path, LibraryFolderId = libraryFolderId }],
MediaFiles = [new MediaFile { Path = path, PathHash = PathUtils.GetPathHash(path), LibraryFolderId = libraryFolderId }],
Streams = []
}
],

2
ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs

@ -252,7 +252,7 @@ public class MovieRepository : IMovieRepository @@ -252,7 +252,7 @@ public class MovieRepository : IMovieRepository
[
new MediaVersion
{
MediaFiles = [new MediaFile { Path = path, LibraryFolderId = libraryFolderId }],
MediaFiles = [new MediaFile { Path = path, PathHash = PathUtils.GetPathHash(path), LibraryFolderId = libraryFolderId }],
Streams = []
}
],

2
ErsatzTV.Infrastructure/Data/Repositories/MusicVideoRepository.cs

@ -233,7 +233,7 @@ public class MusicVideoRepository : IMusicVideoRepository @@ -233,7 +233,7 @@ public class MusicVideoRepository : IMusicVideoRepository
[
new MediaVersion
{
MediaFiles = [new MediaFile { Path = path, LibraryFolderId = libraryFolderId }],
MediaFiles = [new MediaFile { Path = path, PathHash = PathUtils.GetPathHash(path), LibraryFolderId = libraryFolderId }],
Streams = []
}
],

2
ErsatzTV.Infrastructure/Data/Repositories/OtherVideoRepository.cs

@ -207,7 +207,7 @@ public class OtherVideoRepository : IOtherVideoRepository @@ -207,7 +207,7 @@ public class OtherVideoRepository : IOtherVideoRepository
[
new MediaVersion
{
MediaFiles = [new MediaFile { Path = path, LibraryFolderId = libraryFolderId }],
MediaFiles = [new MediaFile { Path = path, PathHash = PathUtils.GetPathHash(path), LibraryFolderId = libraryFolderId }],
Streams = []
}
],

2
ErsatzTV.Infrastructure/Data/Repositories/RemoteStreamRepository.cs

@ -149,7 +149,7 @@ public class RemoteStreamRepository( @@ -149,7 +149,7 @@ public class RemoteStreamRepository(
[
new MediaVersion
{
MediaFiles = [new MediaFile { Path = path, LibraryFolderId = libraryFolderId }],
MediaFiles = [new MediaFile { Path = path, PathHash = PathUtils.GetPathHash(path), LibraryFolderId = libraryFolderId }],
Streams = []
}
],

2
ErsatzTV.Infrastructure/Data/Repositories/SongRepository.cs

@ -134,7 +134,7 @@ public class SongRepository : ISongRepository @@ -134,7 +134,7 @@ public class SongRepository : ISongRepository
[
new MediaVersion
{
MediaFiles = [new MediaFile { Path = path, LibraryFolderId = libraryFolderId }],
MediaFiles = [new MediaFile { Path = path, PathHash = PathUtils.GetPathHash(path), LibraryFolderId = libraryFolderId }],
Streams = []
}
],

2
ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs

@ -773,7 +773,7 @@ public class TelevisionRepository : ITelevisionRepository @@ -773,7 +773,7 @@ public class TelevisionRepository : ITelevisionRepository
[
new MediaVersion
{
MediaFiles = [new MediaFile { Path = path, LibraryFolderId = libraryFolderId }],
MediaFiles = [new MediaFile { Path = path, PathHash = PathUtils.GetPathHash(path), LibraryFolderId = libraryFolderId }],
Streams = []
}
],

1
ErsatzTV.Infrastructure/Data/TvContext.cs

@ -17,6 +17,7 @@ public class TvContext : DbContext @@ -17,6 +17,7 @@ public class TvContext : DbContext
public static string LastInsertedRowId { get; set; } = "last_insert_rowid()";
public static string CaseInsensitiveCollation { get; set; } = "NOCASE";
public static bool IsSqlite { get; set; }
public IDbConnection Connection => Database.GetDbConnection();

32
ErsatzTV/Services/RunOnce/DatabaseMigratorService.cs

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
using ErsatzTV.Core;
using Dapper;
using ErsatzTV.Core;
using ErsatzTV.Infrastructure.Data;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Services.RunOnce;
@ -28,11 +30,39 @@ public class DatabaseMigratorService : BackgroundService @@ -28,11 +30,39 @@ public class DatabaseMigratorService : BackgroundService
using IServiceScope scope = _serviceScopeFactory.CreateScope();
await using TvContext dbContext = scope.ServiceProvider.GetRequiredService<TvContext>();
await dbContext.Database.MigrateAsync("Add_MediaFilePathHash", stoppingToken);
// this can't be part of a migration, so we have to stop here and run some sql
await PopulatePathHashes(dbContext);
// then continue migrating
await dbContext.Database.MigrateAsync(stoppingToken);
await DbInitializer.Initialize(dbContext, stoppingToken);
_systemStartup.DatabaseIsReady();
_logger.LogInformation("Done applying database migrations");
}
private static async Task PopulatePathHashes(TvContext dbContext)
{
if (await dbContext.Connection.ExecuteScalarAsync<int>(
"SELECT COUNT(*) FROM `MediaFile` WHERE `PathHash` IS NULL OR `PathHash` = ''") == 0)
{
return;
}
if (dbContext.Connection is SqliteConnection sqliteConnection)
{
sqliteConnection.CreateFunction("HASH_SHA256", (string text) => PathUtils.GetPathHash(text));
await dbContext.Connection.ExecuteAsync("UPDATE `MediaFile` SET `PathHash` = HASH_SHA256(`Path`);");
}
else
{
// mysql
await dbContext.Connection.ExecuteAsync("UPDATE `MediaFile` SET `PathHash` = sha2(`Path`, 256);");
}
}
}

4
ErsatzTV/Startup.cs

@ -362,6 +362,8 @@ public class Startup @@ -362,6 +362,8 @@ public class Startup
{
if (databaseProvider == Provider.Sqlite.Name)
{
TvContext.IsSqlite = true;
options.UseSqlite(
sqliteConnectionString,
o =>
@ -373,6 +375,8 @@ public class Startup @@ -373,6 +375,8 @@ public class Startup
if (databaseProvider == Provider.MySql.Name)
{
TvContext.IsSqlite = false;
options.UseMySql(
mySqlConnectionString,
ServerVersion.AutoDetect(mySqlConnectionString),

Loading…
Cancel
Save