Metadata Optimizations (#910)

* Added a tooltip to inform user that format and collection filter selections do not only show for the selected library.

* Refactored a lot of code around when we update chapter cover images. Applied an optimization for when we re-calculate volume/series covers, such that it only occurs when the first chapter's image updates.

* Updated code to ensure only lastmodified gets refreshed in metadata since it always follows a scan

* Optimized how metadata is populated on the series. Instead of re-reading the comicInfos, instead I read the data from the underlying chapter entities. This reduces N additional reads AND enables the ability in the future to show/edit chapter level metadata.

* Spelling mistake

* Fixed a concurency issue by not selecting Genres from DB. Added a test for long paths.

* Fixed a bug in filter where collection tag wasn't populating on load

* Cleaned up the logic for changelog to better compare against the installed verison. For nightly users, show the last stable as installed.

* Removed some demo code

* SplitQuery to allow loading tags much faster for series metadata load.
This commit is contained in:
Joseph Milazzo 2022-01-08 06:41:47 -08:00 committed by GitHub
parent c215d5b7a8
commit 0be0e294aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1671 additions and 90 deletions

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using API.Data.Metadata;
using API.Entities;
using API.Entities.Enums;
@ -118,7 +119,7 @@ namespace API.Data
FilePath = filePath,
Format = format,
Pages = pages,
LastModified = DateTime.Now //File.GetLastWriteTime(filePath)
LastModified = File.GetLastWriteTime(filePath) // NOTE: Changed this from DateTime.Now
};
}

View file

@ -85,5 +85,6 @@ namespace API.Data.Metadata
.SingleOrDefault(t => t.ToDescription().ToUpperInvariant().Equals(value.ToUpperInvariant()), Entities.Enums.AgeRating.Unknown);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,108 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Data.Migrations
{
public partial class ChapterMetadataOptimization : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Chapter_Genre_GenreId",
table: "Chapter");
migrationBuilder.DropIndex(
name: "IX_Chapter_GenreId",
table: "Chapter");
migrationBuilder.DropColumn(
name: "GenreId",
table: "Chapter");
migrationBuilder.DropColumn(
name: "FullscreenMode",
table: "AppUserPreferences");
migrationBuilder.AddColumn<string>(
name: "Language",
table: "Chapter",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Summary",
table: "Chapter",
type: "TEXT",
nullable: true);
migrationBuilder.CreateTable(
name: "ChapterGenre",
columns: table => new
{
ChaptersId = table.Column<int>(type: "INTEGER", nullable: false),
GenresId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChapterGenre", x => new { x.ChaptersId, x.GenresId });
table.ForeignKey(
name: "FK_ChapterGenre_Chapter_ChaptersId",
column: x => x.ChaptersId,
principalTable: "Chapter",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ChapterGenre_Genre_GenresId",
column: x => x.GenresId,
principalTable: "Genre",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ChapterGenre_GenresId",
table: "ChapterGenre",
column: "GenresId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ChapterGenre");
migrationBuilder.DropColumn(
name: "Language",
table: "Chapter");
migrationBuilder.DropColumn(
name: "Summary",
table: "Chapter");
migrationBuilder.AddColumn<int>(
name: "GenreId",
table: "Chapter",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "FullscreenMode",
table: "AppUserPreferences",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateIndex(
name: "IX_Chapter_GenreId",
table: "Chapter",
column: "GenreId");
migrationBuilder.AddForeignKey(
name: "FK_Chapter_Genre_GenreId",
table: "Chapter",
column: "GenreId",
principalTable: "Genre",
principalColumn: "Id");
}
}
}

View file

@ -186,9 +186,6 @@ namespace API.Data.Migrations
b.Property<bool>("BookReaderTapToPaginate")
.HasColumnType("INTEGER");
b.Property<int>("FullscreenMode")
.HasColumnType("INTEGER");
b.Property<int>("PageSplitOption")
.HasColumnType("INTEGER");
@ -311,12 +308,12 @@ namespace API.Data.Migrations
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<int?>("GenreId")
.HasColumnType("INTEGER");
b.Property<bool>("IsSpecial")
.HasColumnType("INTEGER");
b.Property<string>("Language")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
@ -332,6 +329,9 @@ namespace API.Data.Migrations
b.Property<DateTime>("ReleaseDate")
.HasColumnType("TEXT");
b.Property<string>("Summary")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
@ -343,8 +343,6 @@ namespace API.Data.Migrations
b.HasKey("Id");
b.HasIndex("GenreId");
b.HasIndex("VolumeId");
b.ToTable("Chapter");
@ -749,6 +747,21 @@ namespace API.Data.Migrations
b.ToTable("AppUserLibrary");
});
modelBuilder.Entity("ChapterGenre", b =>
{
b.Property<int>("ChaptersId")
.HasColumnType("INTEGER");
b.Property<int>("GenresId")
.HasColumnType("INTEGER");
b.HasKey("ChaptersId", "GenresId");
b.HasIndex("GenresId");
b.ToTable("ChapterGenre");
});
modelBuilder.Entity("ChapterPerson", b =>
{
b.Property<int>("ChapterMetadatasId")
@ -1000,10 +1013,6 @@ namespace API.Data.Migrations
modelBuilder.Entity("API.Entities.Chapter", b =>
{
b.HasOne("API.Entities.Genre", null)
.WithMany("Chapters")
.HasForeignKey("GenreId");
b.HasOne("API.Entities.Volume", "Volume")
.WithMany("Chapters")
.HasForeignKey("VolumeId")
@ -1129,6 +1138,21 @@ namespace API.Data.Migrations
.IsRequired();
});
modelBuilder.Entity("ChapterGenre", b =>
{
b.HasOne("API.Entities.Chapter", null)
.WithMany()
.HasForeignKey("ChaptersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("API.Entities.Genre", null)
.WithMany()
.HasForeignKey("GenresId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ChapterPerson", b =>
{
b.HasOne("API.Entities.Chapter", null)
@ -1280,11 +1304,6 @@ namespace API.Data.Migrations
b.Navigation("Files");
});
modelBuilder.Entity("API.Entities.Genre", b =>
{
b.Navigation("Chapters");
});
modelBuilder.Entity("API.Entities.Library", b =>
{
b.Navigation("Folders");

View file

@ -156,6 +156,11 @@ public class SeriesRepository : ISeriesRepository
.Include(s => s.Volumes)
.ThenInclude(v => v.Chapters)
.ThenInclude(c => c.Genres)
.Include(s => s.Volumes)
.ThenInclude(v => v.Chapters)
.ThenInclude(c => c.Tags)
.Include(s => s.Volumes)
.ThenInclude(v => v.Chapters)
@ -186,7 +191,12 @@ public class SeriesRepository : ISeriesRepository
.Include(s => s.Volumes)
.ThenInclude(v => v.Chapters)
.ThenInclude(cm => cm.Tags)
.ThenInclude(c => c.Tags)
.Include(s => s.Volumes)
.ThenInclude(v => v.Chapters)
.ThenInclude(c => c.Genres)
.Include(s => s.Metadata)
.ThenInclude(m => m.Tags)
@ -590,6 +600,7 @@ public class SeriesRepository : ISeriesRepository
.Include(m => m.People)
.AsNoTracking()
.ProjectTo<SeriesMetadataDto>(_mapper.ConfigurationProvider)
.AsSplitQuery()
.SingleOrDefaultAsync();
if (metadataDto != null)