v0.7.3 - The Quality of Life Update (#2036)

* Version bump

* Okay this should be the last (#2037)

* Fixed improper date visualization for reading list detail page.

* Correct not-read badge position (#2034)

---------

Co-authored-by: Andre Smith <Hobogrammer@users.noreply.github.com>

* Bump versions by dotnet-bump-version.

* Merged develop in

---------

Co-authored-by: Andre Smith <Hobogrammer@users.noreply.github.com>
This commit is contained in:
Joe Milazzo 2023-06-07 07:55:53 -05:00 committed by GitHub
parent 51e23b7eca
commit 1b3866568f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
235 changed files with 14827 additions and 21948 deletions

View file

@ -47,6 +47,7 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
public DbSet<FolderPath> FolderPath { get; set; } = null!;
public DbSet<Device> Device { get; set; } = null!;
public DbSet<ServerStatistics> ServerStatistics { get; set; } = null!;
public DbSet<MediaError> MediaError { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder builder)
@ -113,6 +114,17 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
builder.Entity<Library>()
.Property(b => b.ManageReadingLists)
.HasDefaultValue(true);
builder.Entity<Chapter>()
.Property(b => b.WebLinks)
.HasDefaultValue(string.Empty);
builder.Entity<SeriesMetadata>()
.Property(b => b.WebLinks)
.HasDefaultValue(string.Empty);
builder.Entity<Chapter>()
.Property(b => b.ISBN)
.HasDefaultValue(string.Empty);
}

View file

@ -3,7 +3,7 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// v0.7 introduced UTC dates and GMT+1 users would sometimes have dates stored as '0000-12-31 23:00:00'.

View file

@ -3,7 +3,7 @@ using API.Constants;
using API.Entities;
using Microsoft.AspNetCore.Identity;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// New role introduced in v0.5.1. Adds the role to all users.

View file

@ -4,7 +4,7 @@ using API.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// New role introduced in v0.6. Adds the role to all users.

View file

@ -4,7 +4,7 @@ using API.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// Added in v0.7.1.18

View file

@ -3,7 +3,7 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// v0.6.0 introduced a change in how Normalization works and hence every normalized field needs to be re-calculated

View file

@ -3,7 +3,7 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// v0.5.6 introduced Normalized Localized Name, which allows for faster lookups and less memory usage. This migration will calculate them once

View file

@ -4,7 +4,7 @@ using API.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// New role introduced in v0.6. Calculates the Age Rating on all Reading Lists

View file

@ -3,7 +3,7 @@ using System.Linq;
using System.Threading.Tasks;
using API.Services.Tasks;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// In v0.5.3, we removed Light and E-Ink themes. This migration will remove the themes from the DB and default anyone on

View file

@ -0,0 +1,31 @@
using System.Threading.Tasks;
using API.Entities.Enums;
using Microsoft.Extensions.Logging;
namespace API.Data.ManualMigrations;
/// <summary>
/// Added in v0.7.2.7/v0.7.3 in which the ConvertXToWebP Setting keys were removed. This migration will remove them.
/// </summary>
public static class MigrateRemoveWebPSettingRows
{
public static async Task Migrate(IUnitOfWork unitOfWork, ILogger<Program> logger)
{
logger.LogCritical("Running MigrateRemoveWebPSettingRows migration - Please be patient, this may take some time. This is not an error");
var key = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.ConvertBookmarkToWebP);
var key2 = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.ConvertCoverToWebP);
if (key == null && key2 == null)
{
logger.LogCritical("Running MigrateRemoveWebPSettingRows migration - complete. Nothing to do");
return;
}
unitOfWork.SettingsRepository.Remove(key);
unitOfWork.SettingsRepository.Remove(key2);
await unitOfWork.CommitAsync();
logger.LogCritical("Running MigrateRemoveWebPSettingRows migration - Completed. This is not an error");
}
}

View file

@ -10,7 +10,7 @@ using Kavita.Common.EnvironmentInfo;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Data;
namespace API.Data.ManualMigrations;
internal sealed class SeriesRelationMigrationOutput
{

View file

@ -8,7 +8,7 @@ using CsvHelper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// Introduced in v0.6.1.2 and v0.7, this imports to a temp file the existing series relationships. It is a 3 part migration.

View file

@ -3,7 +3,7 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// Introduced in v0.6.1.38 or v0.7.0,

View file

@ -1,7 +1,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace API.Data;
namespace API.Data.ManualMigrations;
/// <summary>
/// Introduced in v0.6.1.8 and v0.7, this adds library ids to all User Progress to allow for easier queries against progress

View file

@ -2,7 +2,9 @@
using System.Linq;
using API.Entities;
using API.Entities.Enums;
using API.Services;
using Kavita.Common.Extensions;
using Nager.ArticleNumber;
namespace API.Data.Metadata;
@ -35,9 +37,21 @@ public class ComicInfo
/// IETF BCP 47 Code to represent the language of the content
/// </summary>
public string LanguageISO { get; set; } = string.Empty;
// ReSharper disable once InconsistentNaming
/// <summary>
/// ISBN for the underlying document
/// </summary>
/// <remarks>ComicInfo.xml will actually output a GTIN (Global Trade Item Number) and it is the responsibility of the Parser to extract the ISBN. EPub will return ISBN.</remarks>
public string Isbn { get; set; } = string.Empty;
/// <summary>
/// This is only for deserialization and used within <see cref="ArchiveService"/>. Use <see cref="Isbn"/> for the actual value.
/// </summary>
public string GTIN { get; set; } = string.Empty;
/// <summary>
/// This is the link to where the data was scraped from
/// </summary>
/// <remarks>This can be comma-separated</remarks>
public string Web { get; set; } = string.Empty;
[System.ComponentModel.DefaultValueAttribute(0)]
public int Day { get; set; } = 0;
@ -137,6 +151,23 @@ public class ComicInfo
info.Characters = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.Characters);
info.Translator = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.Translator);
info.CoverArtist = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.CoverArtist);
// We need to convert GTIN to ISBN
if (!string.IsNullOrEmpty(info.GTIN))
{
// This is likely a valid ISBN
if (info.GTIN[0] == '0')
{
var potentialISBN = info.GTIN.Substring(1, info.GTIN.Length - 1);
if (ArticleNumberHelper.IsValidIsbn13(potentialISBN))
{
info.Isbn = potentialISBN;
}
} else if (ArticleNumberHelper.IsValidIsbn10(info.GTIN) || ArticleNumberHelper.IsValidIsbn13(info.GTIN))
{
info.Isbn = info.GTIN;
}
}
}
/// <summary>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Data.Migrations
{
/// <inheritdoc />
public partial class MediaError : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "MediaError",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Extension = table.Column<string>(type: "TEXT", nullable: true),
FilePath = table.Column<string>(type: "TEXT", nullable: true),
Comment = table.Column<string>(type: "TEXT", nullable: true),
Details = table.Column<string>(type: "TEXT", nullable: true),
Created = table.Column<DateTime>(type: "TEXT", nullable: false),
LastModified = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MediaError", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "MediaError");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Data.Migrations
{
/// <inheritdoc />
public partial class WebLinksForChapter : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "WebLinks",
table: "Chapter",
type: "TEXT",
nullable: true,
defaultValue: string.Empty);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "WebLinks",
table: "Chapter");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Data.Migrations
{
/// <inheritdoc />
public partial class WebLinksForSeries : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "WebLinks",
table: "SeriesMetadata",
type: "TEXT",
nullable: true,
defaultValue: string.Empty);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "WebLinks",
table: "SeriesMetadata");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Data.Migrations
{
/// <inheritdoc />
public partial class ChapterISBN : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ISBN",
table: "Chapter",
type: "TEXT",
nullable: true,
defaultValue: string.Empty);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ISBN",
table: "Chapter");
}
}
}

View file

@ -177,7 +177,7 @@ namespace API.Data.Migrations
b.HasIndex("AppUserId");
b.ToTable("AppUserBookmark");
b.ToTable("AppUserBookmark", (string)null);
});
modelBuilder.Entity("API.Entities.AppUserPreferences", b =>
@ -282,7 +282,7 @@ namespace API.Data.Migrations
b.HasIndex("ThemeId");
b.ToTable("AppUserPreferences");
b.ToTable("AppUserPreferences", (string)null);
});
modelBuilder.Entity("API.Entities.AppUserProgress", b =>
@ -332,7 +332,7 @@ namespace API.Data.Migrations
b.HasIndex("SeriesId");
b.ToTable("AppUserProgresses");
b.ToTable("AppUserProgresses", (string)null);
});
modelBuilder.Entity("API.Entities.AppUserRating", b =>
@ -359,7 +359,7 @@ namespace API.Data.Migrations
b.HasIndex("SeriesId");
b.ToTable("AppUserRating");
b.ToTable("AppUserRating", (string)null);
});
modelBuilder.Entity("API.Entities.AppUserRole", b =>
@ -413,6 +413,11 @@ namespace API.Data.Migrations
b.Property<DateTime>("CreatedUtc")
.HasColumnType("TEXT");
b.Property<string>("ISBN")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("");
b.Property<bool>("IsSpecial")
.HasColumnType("INTEGER");
@ -467,6 +472,11 @@ namespace API.Data.Migrations
b.Property<int>("VolumeId")
.HasColumnType("INTEGER");
b.Property<string>("WebLinks")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("");
b.Property<long>("WordCount")
.HasColumnType("INTEGER");
@ -474,7 +484,7 @@ namespace API.Data.Migrations
b.HasIndex("VolumeId");
b.ToTable("Chapter");
b.ToTable("Chapter", (string)null);
});
modelBuilder.Entity("API.Entities.CollectionTag", b =>
@ -509,7 +519,7 @@ namespace API.Data.Migrations
b.HasIndex("Id", "Promoted")
.IsUnique();
b.ToTable("CollectionTag");
b.ToTable("CollectionTag", (string)null);
});
modelBuilder.Entity("API.Entities.Device", b =>
@ -555,7 +565,7 @@ namespace API.Data.Migrations
b.HasIndex("AppUserId");
b.ToTable("Device");
b.ToTable("Device", (string)null);
});
modelBuilder.Entity("API.Entities.FolderPath", b =>
@ -577,7 +587,7 @@ namespace API.Data.Migrations
b.HasIndex("LibraryId");
b.ToTable("FolderPath");
b.ToTable("FolderPath", (string)null);
});
modelBuilder.Entity("API.Entities.Genre", b =>
@ -597,7 +607,7 @@ namespace API.Data.Migrations
b.HasIndex("NormalizedTitle")
.IsUnique();
b.ToTable("Genre");
b.ToTable("Genre", (string)null);
});
modelBuilder.Entity("API.Entities.Library", b =>
@ -662,7 +672,7 @@ namespace API.Data.Migrations
b.HasKey("Id");
b.ToTable("Library");
b.ToTable("Library", (string)null);
});
modelBuilder.Entity("API.Entities.MangaFile", b =>
@ -711,7 +721,42 @@ namespace API.Data.Migrations
b.HasIndex("ChapterId");
b.ToTable("MangaFile");
b.ToTable("MangaFile", (string)null);
});
modelBuilder.Entity("API.Entities.MediaError", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Comment")
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedUtc")
.HasColumnType("TEXT");
b.Property<string>("Details")
.HasColumnType("TEXT");
b.Property<string>("Extension")
.HasColumnType("TEXT");
b.Property<string>("FilePath")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModifiedUtc")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("MediaError", (string)null);
});
modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b =>
@ -796,6 +841,11 @@ namespace API.Data.Migrations
b.Property<bool>("TranslatorLocked")
.HasColumnType("INTEGER");
b.Property<string>("WebLinks")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("");
b.Property<bool>("WriterLocked")
.HasColumnType("INTEGER");
@ -807,7 +857,7 @@ namespace API.Data.Migrations
b.HasIndex("Id", "SeriesId")
.IsUnique();
b.ToTable("SeriesMetadata");
b.ToTable("SeriesMetadata", (string)null);
});
modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b =>
@ -831,7 +881,7 @@ namespace API.Data.Migrations
b.HasIndex("TargetSeriesId");
b.ToTable("SeriesRelation");
b.ToTable("SeriesRelation", (string)null);
});
modelBuilder.Entity("API.Entities.Person", b =>
@ -851,7 +901,7 @@ namespace API.Data.Migrations
b.HasKey("Id");
b.ToTable("Person");
b.ToTable("Person", (string)null);
});
modelBuilder.Entity("API.Entities.ReadingList", b =>
@ -912,7 +962,7 @@ namespace API.Data.Migrations
b.HasIndex("AppUserId");
b.ToTable("ReadingList");
b.ToTable("ReadingList", (string)null);
});
modelBuilder.Entity("API.Entities.ReadingListItem", b =>
@ -946,7 +996,7 @@ namespace API.Data.Migrations
b.HasIndex("VolumeId");
b.ToTable("ReadingListItem");
b.ToTable("ReadingListItem", (string)null);
});
modelBuilder.Entity("API.Entities.Series", b =>
@ -1045,7 +1095,7 @@ namespace API.Data.Migrations
b.HasIndex("LibraryId");
b.ToTable("Series");
b.ToTable("Series", (string)null);
});
modelBuilder.Entity("API.Entities.ServerSetting", b =>
@ -1062,7 +1112,7 @@ namespace API.Data.Migrations
b.HasKey("Key");
b.ToTable("ServerSetting");
b.ToTable("ServerSetting", (string)null);
});
modelBuilder.Entity("API.Entities.ServerStatistics", b =>
@ -1100,7 +1150,7 @@ namespace API.Data.Migrations
b.HasKey("Id");
b.ToTable("ServerStatistics");
b.ToTable("ServerStatistics", (string)null);
});
modelBuilder.Entity("API.Entities.SiteTheme", b =>
@ -1138,7 +1188,7 @@ namespace API.Data.Migrations
b.HasKey("Id");
b.ToTable("SiteTheme");
b.ToTable("SiteTheme", (string)null);
});
modelBuilder.Entity("API.Entities.Tag", b =>
@ -1158,7 +1208,7 @@ namespace API.Data.Migrations
b.HasIndex("NormalizedTitle")
.IsUnique();
b.ToTable("Tag");
b.ToTable("Tag", (string)null);
});
modelBuilder.Entity("API.Entities.Volume", b =>
@ -1210,7 +1260,7 @@ namespace API.Data.Migrations
b.HasIndex("SeriesId");
b.ToTable("Volume");
b.ToTable("Volume", (string)null);
});
modelBuilder.Entity("AppUserLibrary", b =>
@ -1225,7 +1275,7 @@ namespace API.Data.Migrations
b.HasIndex("LibrariesId");
b.ToTable("AppUserLibrary");
b.ToTable("AppUserLibrary", (string)null);
});
modelBuilder.Entity("ChapterGenre", b =>
@ -1240,7 +1290,7 @@ namespace API.Data.Migrations
b.HasIndex("GenresId");
b.ToTable("ChapterGenre");
b.ToTable("ChapterGenre", (string)null);
});
modelBuilder.Entity("ChapterPerson", b =>
@ -1255,7 +1305,7 @@ namespace API.Data.Migrations
b.HasIndex("PeopleId");
b.ToTable("ChapterPerson");
b.ToTable("ChapterPerson", (string)null);
});
modelBuilder.Entity("ChapterTag", b =>
@ -1270,7 +1320,7 @@ namespace API.Data.Migrations
b.HasIndex("TagsId");
b.ToTable("ChapterTag");
b.ToTable("ChapterTag", (string)null);
});
modelBuilder.Entity("CollectionTagSeriesMetadata", b =>
@ -1285,7 +1335,7 @@ namespace API.Data.Migrations
b.HasIndex("SeriesMetadatasId");
b.ToTable("CollectionTagSeriesMetadata");
b.ToTable("CollectionTagSeriesMetadata", (string)null);
});
modelBuilder.Entity("GenreSeriesMetadata", b =>
@ -1300,7 +1350,7 @@ namespace API.Data.Migrations
b.HasIndex("SeriesMetadatasId");
b.ToTable("GenreSeriesMetadata");
b.ToTable("GenreSeriesMetadata", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
@ -1399,7 +1449,7 @@ namespace API.Data.Migrations
b.HasIndex("SeriesMetadatasId");
b.ToTable("PersonSeriesMetadata");
b.ToTable("PersonSeriesMetadata", (string)null);
});
modelBuilder.Entity("SeriesMetadataTag", b =>
@ -1414,7 +1464,7 @@ namespace API.Data.Migrations
b.HasIndex("TagsId");
b.ToTable("SeriesMetadataTag");
b.ToTable("SeriesMetadataTag", (string)null);
});
modelBuilder.Entity("API.Entities.AppUserBookmark", b =>

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Data.ManualMigrations;
using API.DTOs;
using API.Entities;
using API.Entities.Enums;
@ -25,6 +26,7 @@ public interface IAppUserProgressRepository
Task<IEnumerable<AppUserProgress>> GetUserProgressForSeriesAsync(int seriesId, int userId);
Task<IEnumerable<AppUserProgress>> GetAllProgress();
Task<ProgressDto> GetUserProgressDtoAsync(int chapterId, int userId);
Task<bool> AnyUserProgressForSeriesAsync(int seriesId, int userId);
}
public class AppUserProgressRepository : IAppUserProgressRepository
@ -128,6 +130,13 @@ public class AppUserProgressRepository : IAppUserProgressRepository
.FirstOrDefaultAsync();
}
public async Task<bool> AnyUserProgressForSeriesAsync(int seriesId, int userId)
{
return await _context.AppUserProgresses
.Where(p => p.SeriesId == seriesId && p.AppUserId == userId && p.PagesRead > 0)
.AnyAsync();
}
public async Task<AppUserProgress?> GetUserProgressAsync(int chapterId, int userId)
{
return await _context.AppUserProgresses

View file

@ -6,6 +6,7 @@ using API.DTOs;
using API.DTOs.Metadata;
using API.DTOs.Reader;
using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Extensions.QueryExtensions;
using AutoMapper;
@ -36,7 +37,7 @@ public interface IChapterRepository
Task<IList<MangaFile>> GetFilesForChaptersAsync(IReadOnlyList<int> chapterIds);
Task<string?> GetChapterCoverImageAsync(int chapterId);
Task<IList<string>> GetAllCoverImagesAsync();
Task<IList<Chapter>> GetAllChaptersWithNonWebPCovers();
Task<IList<Chapter>> GetAllChaptersWithCoversInDifferentEncoding(EncodeFormat format);
Task<IEnumerable<string>> GetCoverImagesForLockedChaptersAsync();
Task<ChapterDto> AddChapterModifiers(int userId, ChapterDto chapter);
}
@ -208,10 +209,11 @@ public class ChapterRepository : IChapterRepository
.ToListAsync())!;
}
public async Task<IList<Chapter>> GetAllChaptersWithNonWebPCovers()
public async Task<IList<Chapter>> GetAllChaptersWithCoversInDifferentEncoding(EncodeFormat format)
{
var extension = format.GetExtension();
return await _context.Chapter
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(".webp"))
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(extension))
.ToListAsync();
}

View file

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using API.Data.Misc;
using API.DTOs.CollectionTags;
using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Extensions.QueryExtensions;
using AutoMapper;
@ -34,7 +35,7 @@ public interface ICollectionTagRepository
Task<IEnumerable<CollectionTag>> GetAllTagsAsync(CollectionTagIncludes includes = CollectionTagIncludes.None);
Task<IList<string>> GetAllCoverImagesAsync();
Task<bool> TagExists(string title);
Task<IList<CollectionTag>> GetAllWithNonWebPCovers();
Task<IList<CollectionTag>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
}
public class CollectionTagRepository : ICollectionTagRepository
{
@ -108,10 +109,11 @@ public class CollectionTagRepository : ICollectionTagRepository
.AnyAsync(x => x.NormalizedTitle != null && x.NormalizedTitle.Equals(normalized));
}
public async Task<IList<CollectionTag>> GetAllWithNonWebPCovers()
public async Task<IList<CollectionTag>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat)
{
var extension = encodeFormat.GetExtension();
return await _context.CollectionTag
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(".webp"))
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(extension))
.ToListAsync();
}

View file

@ -52,7 +52,7 @@ public interface ILibraryRepository
Task<string?> GetLibraryCoverImageAsync(int libraryId);
Task<IList<string>> GetAllCoverImagesAsync();
Task<IDictionary<int, LibraryType>> GetLibraryTypesForIdsAsync(IEnumerable<int> libraryIds);
Task<IList<Library>> GetAllWithNonWebPCovers();
Task<IList<Library>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
}
public class LibraryRepository : ILibraryRepository
@ -170,10 +170,7 @@ public class LibraryRepository : ILibraryRepository
var c = sortChar;
var isAlpha = char.IsLetter(sortChar);
if (!isAlpha) c = '#';
if (!firstCharacterMap.ContainsKey(c))
{
firstCharacterMap[c] = 0;
}
firstCharacterMap.TryAdd(c, 0);
firstCharacterMap[c] += 1;
}
@ -371,10 +368,11 @@ public class LibraryRepository : ILibraryRepository
return dict;
}
public async Task<IList<Library>> GetAllWithNonWebPCovers()
public async Task<IList<Library>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat)
{
var extension = encodeFormat.GetExtension();
return await _context.Library
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(".webp"))
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(extension))
.ToListAsync();
}
}

View file

@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs.MediaErrors;
using API.Entities;
using API.Helpers;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
namespace API.Data.Repositories;
public interface IMediaErrorRepository
{
void Attach(MediaError error);
void Remove(MediaError error);
Task<MediaError> Find(string filename);
Task<PagedList<MediaErrorDto>> GetAllErrorDtosAsync(UserParams userParams);
IEnumerable<MediaErrorDto> GetAllErrorDtosAsync();
Task<bool> ExistsAsync(MediaError error);
Task DeleteAll();
}
public class MediaErrorRepository : IMediaErrorRepository
{
private readonly DataContext _context;
private readonly IMapper _mapper;
public MediaErrorRepository(DataContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public void Attach(MediaError? error)
{
if (error == null) return;
_context.MediaError.Attach(error);
}
public void Remove(MediaError? error)
{
if (error == null) return;
_context.MediaError.Remove(error);
}
public Task<MediaError?> Find(string filename)
{
return _context.MediaError.Where(e => e.FilePath == filename).SingleOrDefaultAsync();
}
public Task<PagedList<MediaErrorDto>> GetAllErrorDtosAsync(UserParams userParams)
{
var query = _context.MediaError
.OrderByDescending(m => m.Created)
.ProjectTo<MediaErrorDto>(_mapper.ConfigurationProvider)
.AsNoTracking();
return PagedList<MediaErrorDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
}
public IEnumerable<MediaErrorDto> GetAllErrorDtosAsync()
{
var query = _context.MediaError
.OrderByDescending(m => m.Created)
.ProjectTo<MediaErrorDto>(_mapper.ConfigurationProvider)
.AsNoTracking();
return query.AsEnumerable();
}
public Task<bool> ExistsAsync(MediaError error)
{
return _context.MediaError.AnyAsync(m => m.FilePath.Equals(error.FilePath)
&& m.Comment.Equals(error.Comment)
&& m.Details.Equals(error.Details)
);
}
public async Task DeleteAll()
{
_context.MediaError.RemoveRange(await _context.MediaError.ToListAsync());
await _context.SaveChangesAsync();
}
}

View file

@ -45,7 +45,7 @@ public interface IReadingListRepository
Task<IList<string>> GetAllCoverImagesAsync();
Task<bool> ReadingListExists(string name);
IEnumerable<PersonDto> GetReadingListCharactersAsync(int readingListId);
Task<IList<ReadingList>> GetAllWithNonWebPCovers();
Task<IList<ReadingList>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
Task<IList<string>> GetFirstFourCoverImagesByReadingListId(int readingListId);
Task<int> RemoveReadingListsWithoutSeries();
Task<ReadingList?> GetReadingListByTitleAsync(string name, int userId, ReadingListIncludes includes = ReadingListIncludes.Items);
@ -110,10 +110,11 @@ public class ReadingListRepository : IReadingListRepository
.AsEnumerable();
}
public async Task<IList<ReadingList>> GetAllWithNonWebPCovers()
public async Task<IList<ReadingList>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat)
{
var extension = encodeFormat.GetExtension();
return await _context.ReadingList
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(".webp"))
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(extension))
.ToListAsync();
}

View file

@ -4,6 +4,7 @@ using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using API.Data.ManualMigrations;
using API.Data.Misc;
using API.Data.Scanner;
using API.DTOs;
@ -79,7 +80,7 @@ public interface ISeriesRepository
/// <returns></returns>
Task<SearchResultGroupDto> SearchSeries(int userId, bool isAdmin, IList<int> libraryIds, string searchQuery);
Task<IEnumerable<Series>> GetSeriesForLibraryIdAsync(int libraryId, SeriesIncludes includes = SeriesIncludes.None);
Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId, int userId);
Task<SeriesDto?> GetSeriesDtoByIdAsync(int seriesId, int userId);
Task<Series?> GetSeriesByIdAsync(int seriesId, SeriesIncludes includes = SeriesIncludes.Volumes | SeriesIncludes.Metadata);
Task<IList<Series>> GetSeriesByIdsAsync(IList<int> seriesIds);
Task<int[]> GetChapterIdsForSeriesAsync(IList<int> seriesIds);
@ -132,7 +133,7 @@ public interface ISeriesRepository
Task<IDictionary<int, int>> GetLibraryIdsForSeriesAsync();
Task<IList<SeriesMetadataDto>> GetSeriesMetadataForIds(IEnumerable<int> seriesIds);
Task<IList<Series>> GetAllWithNonWebPCovers(bool customOnly = true);
Task<IList<Series>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat, bool customOnly = true);
}
public class SeriesRepository : ISeriesRepository
@ -347,11 +348,11 @@ public class SeriesRepository : ISeriesRepository
result.Series = _context.Series
.Where(s => libraryIds.Contains(s.LibraryId))
.Where(s => (EF.Functions.Like(s.Name, $"%{searchQuery}%")
.Where(s => EF.Functions.Like(s.Name, $"%{searchQuery}%")
|| (s.OriginalName != null && EF.Functions.Like(s.OriginalName, $"%{searchQuery}%"))
|| (s.LocalizedName != null && EF.Functions.Like(s.LocalizedName, $"%{searchQuery}%"))
|| (EF.Functions.Like(s.NormalizedName, $"%{searchQueryNormalized}%"))
|| (hasYearInQuery && s.Metadata.ReleaseYear == yearComparison)))
|| (hasYearInQuery && s.Metadata.ReleaseYear == yearComparison))
.RestrictAgainstAgeRestriction(userRating)
.Include(s => s.Library)
.OrderBy(s => s.SortName!.ToLower())
@ -429,7 +430,9 @@ public class SeriesRepository : ISeriesRepository
result.Chapters = await _context.Chapter
.Include(c => c.Files)
.Where(c => EF.Functions.Like(c.TitleName, $"%{searchQuery}%"))
.Where(c => EF.Functions.Like(c.TitleName, $"%{searchQuery}%")
|| EF.Functions.Like(c.ISBN, $"%{searchQuery}%")
)
.Where(c => c.Files.All(f => fileIds.Contains(f.Id)))
.AsSplitQuery()
.Take(maxRecords)
@ -439,11 +442,13 @@ public class SeriesRepository : ISeriesRepository
return result;
}
public async Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId, int userId)
public async Task<SeriesDto?> GetSeriesDtoByIdAsync(int seriesId, int userId)
{
var series = await _context.Series.Where(x => x.Id == seriesId)
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
.SingleAsync();
.SingleOrDefaultAsync();
if (series == null) return null;
var seriesList = new List<SeriesDto>() {series};
await AddSeriesModifiers(userId, seriesList);
@ -565,12 +570,14 @@ public class SeriesRepository : ISeriesRepository
/// Returns custom images only
/// </summary>
/// <returns></returns>
public async Task<IList<Series>> GetAllWithNonWebPCovers(bool customOnly = true)
public async Task<IList<Series>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat,
bool customOnly = true)
{
var extension = encodeFormat.GetExtension();
var prefix = ImageService.GetSeriesFormat(0).Replace("0", string.Empty);
return await _context.Series
.Where(c => !string.IsNullOrEmpty(c.CoverImage)
&& !c.CoverImage.EndsWith(".webp")
&& !c.CoverImage.EndsWith(extension)
&& (!customOnly || c.CoverImage.StartsWith(prefix)))
.ToListAsync();
}

View file

@ -15,6 +15,7 @@ public interface ISettingsRepository
Task<ServerSettingDto> GetSettingsDtoAsync();
Task<ServerSetting> GetSettingAsync(ServerSettingKey key);
Task<IEnumerable<ServerSetting>> GetSettingsAsync();
void Remove(ServerSetting setting);
}
public class SettingsRepository : ISettingsRepository
{
@ -32,6 +33,11 @@ public class SettingsRepository : ISettingsRepository
_context.Entry(settings).State = EntityState.Modified;
}
public void Remove(ServerSetting setting)
{
_context.Remove(setting);
}
public async Task<ServerSettingDto> GetSettingsDtoAsync()
{
var settings = await _context.ServerSetting

View file

@ -4,10 +4,12 @@ using System.Linq;
using System.Threading.Tasks;
using API.DTOs;
using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Services;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Kavita.Common;
using Microsoft.EntityFrameworkCore;
namespace API.Data.Repositories;
@ -26,7 +28,7 @@ public interface IVolumeRepository
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> seriesIds, bool includeChapters = false);
Task<IEnumerable<Volume>> GetVolumes(int seriesId);
Task<Volume?> GetVolumeByIdAsync(int volumeId);
Task<IList<Volume>> GetAllWithNonWebPCovers();
Task<IList<Volume>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
}
public class VolumeRepository : IVolumeRepository
{
@ -200,10 +202,11 @@ public class VolumeRepository : IVolumeRepository
return await _context.Volume.SingleOrDefaultAsync(x => x.Id == volumeId);
}
public async Task<IList<Volume>> GetAllWithNonWebPCovers()
public async Task<IList<Volume>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat)
{
var extension = encodeFormat.GetExtension();
return await _context.Volume
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(".webp"))
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(extension))
.ToListAsync();
}

View file

@ -101,12 +101,11 @@ public static class Seed
new() {Key = ServerSettingKey.InstallVersion, Value = BuildInfo.Version.ToString()},
new() {Key = ServerSettingKey.BookmarkDirectory, Value = directoryService.BookmarkDirectory},
new() {Key = ServerSettingKey.EmailServiceUrl, Value = EmailService.DefaultApiUrl},
new() {Key = ServerSettingKey.ConvertBookmarkToWebP, Value = "false"},
new() {Key = ServerSettingKey.TotalBackups, Value = "30"},
new() {Key = ServerSettingKey.TotalLogs, Value = "30"},
new() {Key = ServerSettingKey.EnableFolderWatching, Value = "false"},
new() {Key = ServerSettingKey.ConvertCoverToWebP, Value = "false"},
new() {Key = ServerSettingKey.HostName, Value = string.Empty},
new() {Key = ServerSettingKey.EncodeMediaAs, Value = EncodeFormat.PNG.ToString()},
}.ToArray());
foreach (var defaultSetting in DefaultSettings)

View file

@ -25,6 +25,7 @@ public interface IUnitOfWork
ISiteThemeRepository SiteThemeRepository { get; }
IMangaFileRepository MangaFileRepository { get; }
IDeviceRepository DeviceRepository { get; }
IMediaErrorRepository MediaErrorRepository { get; }
bool Commit();
Task<bool> CommitAsync();
bool HasChanges();
@ -62,6 +63,7 @@ public class UnitOfWork : IUnitOfWork
public ISiteThemeRepository SiteThemeRepository => new SiteThemeRepository(_context, _mapper);
public IMangaFileRepository MangaFileRepository => new MangaFileRepository(_context);
public IDeviceRepository DeviceRepository => new DeviceRepository(_context, _mapper);
public IMediaErrorRepository MediaErrorRepository => new MediaErrorRepository(_context, _mapper);
/// <summary>
/// Commits changes to the DB. Completes the open transaction.