OPDS-PS v1.2 Support + a few bugfixes (#1869)

* Fixed up a localization lookup test case

* Refactored some webp to a unified method

* Cleaned up some code

* Expanded webp conversion for covers to all entities

* Code cleanup

* Prompt the user when they are about to delete multiple series via bulk actions

* Aligned Kavita to OPDS-PS 1.2.

* Fixed a bug where clearing metadata filter of series name didn't clear the actual field.

* Added some documentation

* Refactored how covert covers to webp works. Now we will handle all custom covers for all entities. Volumes and Series will not be touched but instead be updated via a RefreshCovers call. This will fix up the references much faster.

* Fixed up the OPDS-PS 1.2 attributes to only show on PS links
This commit is contained in:
Joe Milazzo 2023-03-09 18:41:42 -06:00 committed by GitHub
parent 1f34068662
commit 47269b4c51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 334 additions and 99 deletions

View file

@ -33,6 +33,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();
}
public class CollectionTagRepository : ICollectionTagRepository
{
@ -106,6 +107,13 @@ public class CollectionTagRepository : ICollectionTagRepository
.AnyAsync(x => x.NormalizedTitle != null && x.NormalizedTitle.Equals(normalized));
}
public async Task<IList<CollectionTag>> GetAllWithNonWebPCovers()
{
return await _context.CollectionTag
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(".webp"))
.ToListAsync();
}
public async Task<IEnumerable<CollectionTagDto>> GetAllTagDtosAsync()
{

View file

@ -51,6 +51,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();
}
public class LibraryRepository : ILibraryRepository
@ -368,4 +369,11 @@ public class LibraryRepository : ILibraryRepository
return dict;
}
public async Task<IList<Library>> GetAllWithNonWebPCovers()
{
return await _context.Library
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(".webp"))
.ToListAsync();
}
}

View file

@ -36,6 +36,7 @@ public interface IReadingListRepository
Task<bool> ReadingListExists(string name);
Task<List<ReadingList>> GetAllReadingListsAsync();
IEnumerable<PersonDto> GetReadingListCharactersAsync(int readingListId);
Task<IList<ReadingList>> GetAllWithNonWebPCovers();
}
public class ReadingListRepository : IReadingListRepository
@ -106,6 +107,13 @@ public class ReadingListRepository : IReadingListRepository
.AsEnumerable();
}
public async Task<IList<ReadingList>> GetAllWithNonWebPCovers()
{
return await _context.ReadingList
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(".webp"))
.ToListAsync();
}
public void Remove(ReadingListItem item)
{
_context.ReadingListItem.Remove(item);

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@ -130,6 +131,7 @@ public interface ISeriesRepository
Task<IDictionary<int, int>> GetLibraryIdsForSeriesAsync();
Task<IList<SeriesMetadataDto>> GetSeriesMetadataForIds(IEnumerable<int> seriesIds);
Task<IList<Series>> GetAllWithNonWebPCovers(bool customOnly = true);
}
public class SeriesRepository : ISeriesRepository
@ -560,6 +562,21 @@ public class SeriesRepository : ISeriesRepository
}
/// <summary>
/// Returns custom images only
/// </summary>
/// <returns></returns>
public async Task<IList<Series>> GetAllWithNonWebPCovers(bool customOnly = true)
{
var prefix = ImageService.GetSeriesFormat(0).Replace("0", string.Empty);
return await _context.Series
.Where(c => !string.IsNullOrEmpty(c.CoverImage)
&& !c.CoverImage.EndsWith(".webp")
&& (!customOnly || c.CoverImage.StartsWith(prefix)))
.ToListAsync();
}
public async Task AddSeriesModifiers(int userId, List<SeriesDto> series)
{
var userProgress = await _context.AppUserProgresses
@ -1262,38 +1279,40 @@ public class SeriesRepository : ISeriesRepository
/// <param name="format"></param>
/// <param name="withFullIncludes">Defaults to true. This will query against all foreign keys (deep). If false, just the series will come back</param>
/// <returns></returns>
public Task<Series?> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId, MangaFormat format, bool withFullIncludes = true)
public Task<Series?> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId,
MangaFormat format, bool withFullIncludes = true)
{
var normalizedSeries = seriesName.ToNormalized();
var normalizedLocalized = localizedName.ToNormalized();
var query = _context.Series
.Where(s => s.LibraryId == libraryId)
.Where(s => s.Format == format && format != MangaFormat.Unknown)
.Where(s => s.NormalizedName.Equals(normalizedSeries)
|| (s.NormalizedLocalizedName == normalizedSeries)
|| (s.OriginalName == seriesName));
.Where(s =>
s.NormalizedName.Equals(normalizedSeries)
|| s.NormalizedName.Equals(normalizedLocalized)
if (!string.IsNullOrEmpty(normalizedLocalized))
{
// TODO: Apply WhereIf
query = query.Where(s =>
s.NormalizedName.Equals(normalizedLocalized)
|| (s.NormalizedLocalizedName != null && s.NormalizedLocalizedName.Equals(normalizedLocalized)));
}
|| s.NormalizedLocalizedName.Equals(normalizedSeries)
|| (!string.IsNullOrEmpty(normalizedLocalized) && s.NormalizedLocalizedName.Equals(normalizedLocalized))
|| (s.OriginalName != null && s.OriginalName.Equals(seriesName))
);
if (!withFullIncludes)
{
return query.SingleOrDefaultAsync();
}
#nullable disable
return query.Include(s => s.Metadata)
query = query.Include(s => s.Library)
.Include(s => s.Metadata)
.ThenInclude(m => m.People)
.Include(s => s.Metadata)
.ThenInclude(m => m.Genres)
.Include(s => s.Library)
.Include(s => s.Metadata)
.ThenInclude(m => m.Tags)
.Include(s => s.Volumes)
.ThenInclude(v => v.Chapters)
.ThenInclude(cm => cm.People)
@ -1306,15 +1325,12 @@ public class SeriesRepository : ISeriesRepository
.ThenInclude(v => v.Chapters)
.ThenInclude(c => c.Genres)
.Include(s => s.Metadata)
.ThenInclude(m => m.Tags)
.Include(s => s.Volumes)
.ThenInclude(v => v.Chapters)
.ThenInclude(c => c.Files)
.AsSplitQuery()
.SingleOrDefaultAsync();
.AsSplitQuery();
return query.SingleOrDefaultAsync();
#nullable enable
}

View file

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using API.DTOs;
using API.Entities;
using API.Extensions;
using API.Services;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
@ -24,6 +25,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();
}
public class VolumeRepository : IVolumeRepository
{
@ -195,6 +197,13 @@ public class VolumeRepository : IVolumeRepository
return await _context.Volume.SingleOrDefaultAsync(x => x.Id == volumeId);
}
public async Task<IList<Volume>> GetAllWithNonWebPCovers()
{
return await _context.Volume
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(".webp"))
.ToListAsync();
}
private static void SortSpecialChapters(IEnumerable<VolumeDto> volumes)
{