Co-authored-by: Zeoic <zeorgaming@gmail.com>
Co-authored-by: Fesaa <77553571+Fesaa@users.noreply.github.com>
This commit is contained in:
Joe Milazzo 2025-03-01 17:17:57 -06:00 committed by GitHub
parent b38400c092
commit 0ffe0228e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 339 additions and 115 deletions

View file

@ -12,10 +12,11 @@
<LangVersion>latestmajor</LangVersion>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="Build" Condition=" '$(Configuration)' == 'Debug' ">
<Delete Files="../openapi.json" />
<Exec Command="swagger tofile --output ../openapi.json bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).dll v1" />
</Target>
<!-- Moved to GA -->
<!-- <Target Name="PostBuild" AfterTargets="Build" Condition=" '$(Configuration)' == 'Debug' ">-->
<!-- <Delete Files="../openapi.json" />-->
<!-- <Exec Command="swagger tofile &#45;&#45;output ../openapi.json bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).dll v1" />-->
<!-- </Target>-->
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>false</DebugSymbols>

View file

@ -652,9 +652,9 @@ public class SeriesController : BaseApiController
/// <param name="seriesId"></param>
/// <returns></returns>
[HttpPost("update-match")]
public ActionResult UpdateSeriesMatch([FromQuery] int seriesId, [FromQuery] int aniListId)
public ActionResult UpdateSeriesMatch([FromQuery] int seriesId, [FromQuery] int aniListId, [FromQuery] long? malId)
{
BackgroundJob.Enqueue(() => _externalMetadataService.FixSeriesMatch(seriesId, aniListId));
BackgroundJob.Enqueue(() => _externalMetadataService.FixSeriesMatch(seriesId, aniListId, malId));
return Ok();
}

View file

@ -213,9 +213,10 @@ public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepositor
{
return await _context.Series
.Where(s => !ExternalMetadataService.NonEligibleLibraryTypes.Contains(s.Library.Type))
.WhereIf(includeStaleData, s => s.ExternalSeriesMetadata == null || s.ExternalSeriesMetadata.ValidUntilUtc < DateTime.UtcNow)
.Where(s => s.ExternalSeriesMetadata == null || s.ExternalSeriesMetadata.ValidUntilUtc == DateTime.MinValue)
.Where(s => s.Library.AllowMetadataMatching)
.WhereIf(includeStaleData, s => s.ExternalSeriesMetadata == null || s.ExternalSeriesMetadata.ValidUntilUtc < DateTime.UtcNow)
.Where(s => s.ExternalSeriesMetadata == null || s.ExternalSeriesMetadata.AniListId == 0)
.Where(s => !s.IsBlacklisted && !s.DontMatch)
.OrderByDescending(s => s.Library.Type)
.ThenBy(s => s.NormalizedName)
.Select(s => s.Id)
@ -229,6 +230,7 @@ public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepositor
.Include(s => s.Library)
.Include(s => s.ExternalSeriesMetadata)
.Where(s => !ExternalMetadataService.NonEligibleLibraryTypes.Contains(s.Library.Type))
.Where(s => s.Library.AllowMetadataMatching)
.FilterMatchState(filter.MatchStateOption)
.OrderBy(s => s.NormalizedName)
.ProjectTo<ManageMatchSeriesDto>(_mapper.ConfigurationProvider)

View file

@ -18,6 +18,7 @@ using Kavita.Common.Extensions;
using Microsoft.EntityFrameworkCore;
namespace API.Data.Repositories;
#nullable enable
[Flags]
public enum LibraryIncludes
@ -260,7 +261,7 @@ public class LibraryRepository : ILibraryRepository
public async Task<IList<LanguageDto>> GetAllLanguagesForLibrariesAsync(List<int>? libraryIds)
{
var ret = await _context.Series
.WhereIf(libraryIds is {Count: > 0} , s => libraryIds.Contains(s.LibraryId))
.WhereIf(libraryIds is {Count: > 0} , s => libraryIds!.Contains(s.LibraryId))
.Select(s => s.Metadata.Language)
.AsSplitQuery()
.AsNoTracking()

View file

@ -19,11 +19,13 @@ public interface IScrobbleRepository
void Attach(ScrobbleError error);
void Remove(ScrobbleEvent evt);
void Remove(IEnumerable<ScrobbleEvent> events);
void Remove(IEnumerable<ScrobbleError> errors);
void Update(ScrobbleEvent evt);
Task<IList<ScrobbleEvent>> GetByEvent(ScrobbleEventType type, bool isProcessed = false);
Task<IList<ScrobbleEvent>> GetProcessedEvents(int daysAgo);
Task<bool> Exists(int userId, int seriesId, ScrobbleEventType eventType);
Task<IEnumerable<ScrobbleErrorDto>> GetScrobbleErrors();
Task<IList<ScrobbleError>> GetAllScrobbleErrorsForSeries(int seriesId);
Task ClearScrobbleErrors();
Task<bool> HasErrorForSeries(int seriesId);
Task<ScrobbleEvent?> GetEvent(int userId, int seriesId, ScrobbleEventType eventType);
@ -66,6 +68,11 @@ public class ScrobbleRepository : IScrobbleRepository
_context.ScrobbleEvent.RemoveRange(events);
}
public void Remove(IEnumerable<ScrobbleError> errors)
{
_context.ScrobbleError.RemoveRange(errors);
}
public void Update(ScrobbleEvent evt)
{
_context.Entry(evt).State = EntityState.Modified;
@ -113,6 +120,13 @@ public class ScrobbleRepository : IScrobbleRepository
.ToListAsync();
}
public async Task<IList<ScrobbleError>> GetAllScrobbleErrorsForSeries(int seriesId)
{
return await _context.ScrobbleError
.Where(e => e.SeriesId == seriesId)
.ToListAsync();
}
public async Task ClearScrobbleErrors()
{
_context.ScrobbleError.RemoveRange(_context.ScrobbleError);
@ -161,4 +175,5 @@ public class ScrobbleRepository : IScrobbleRepository
return await _context.ScrobbleEvent.Where(e => e.SeriesId == seriesId)
.ToListAsync();
}
}

View file

@ -276,18 +276,18 @@ public static class Seed
await context.SaveChangesAsync();
// Port, IpAddresses and LoggingLevel are managed in appSettings.json. Update the DB values to match
context.ServerSetting.First(s => s.Key == ServerSettingKey.Port).Value =
(await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.Port)).Value =
Configuration.Port + string.Empty;
context.ServerSetting.First(s => s.Key == ServerSettingKey.IpAddresses).Value =
(await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.IpAddresses)).Value =
Configuration.IpAddresses;
context.ServerSetting.First(s => s.Key == ServerSettingKey.CacheDirectory).Value =
(await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.CacheDirectory)).Value =
directoryService.CacheDirectory + string.Empty;
context.ServerSetting.First(s => s.Key == ServerSettingKey.BackupDirectory).Value =
(await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.BackupDirectory)).Value =
DirectoryService.BackupDirectory + string.Empty;
context.ServerSetting.First(s => s.Key == ServerSettingKey.CacheSize).Value =
(await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.CacheSize)).Value =
Configuration.CacheSize + string.Empty;
await context.SaveChangesAsync();
await context.SaveChangesAsync();
}
public static async Task SeedMetadataSettings(DataContext context)

View file

@ -252,8 +252,6 @@ public static class SeriesFilter
if (!condition) return queryable;
var subQuery = queryable
.Include(s => s.Progress)
.Where(s => s.Progress != null)
.Select(s => new
{
SeriesId = s.Id,
@ -372,7 +370,7 @@ public static class SeriesFilter
var subQuery = queryable
.Include(s => s.Progress)
.Where(s => s.Progress != null)
.Where(s => s.Progress.Any())
.Select(s => new
{
SeriesId = s.Id,
@ -435,7 +433,7 @@ public static class SeriesFilter
var subQuery = queryable
.Include(s => s.Progress)
.Where(s => s.Progress != null)
.Where(s => s.Progress.Any())
.Select(s => new
{
SeriesId = s.Id,

View file

@ -311,8 +311,16 @@ public class BookService : IBookService
var imageFile = GetKeyForImage(book, image.Attributes[key].Value);
image.Attributes.Remove(key);
// UrlEncode here to transform ../ into an escaped version, which avoids blocking on nginx
image.Attributes.Add(key, $"{apiBase}" + Uri.EscapeDataString(imageFile));
if (!imageFile.StartsWith("http"))
{
// UrlEncode here to transform ../ into an escaped version, which avoids blocking on nginx
image.Attributes.Add(key, $"{apiBase}" + Uri.EscapeDataString(imageFile));
}
else
{
image.Attributes.Add(key, imageFile);
}
// Add a custom class that the reader uses to ensure images stay within reader
parent.AddClass("kavita-scale-width-container");

View file

@ -49,7 +49,7 @@ public interface IExternalMetadataService
Task<IList<MalStackDto>> GetStacksForUser(int userId);
Task<IList<ExternalSeriesMatchDto>> MatchSeries(MatchSeriesDto dto);
Task FixSeriesMatch(int seriesId, int anilistId);
Task FixSeriesMatch(int seriesId, int anilistId, long? malId);
Task UpdateSeriesDontMatch(int seriesId, bool dontMatch);
Task<bool> WriteExternalMetadataToSeries(ExternalSeriesDetailDto externalMetadata, int seriesId);
}
@ -111,7 +111,7 @@ public class ExternalMetadataService : IExternalMetadataService
public async Task FetchExternalDataTask()
{
// Find all Series that are eligible and limit
var ids = await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesThatNeedExternalMetadata(25);
var ids = await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesThatNeedExternalMetadata(25, false);
if (ids.Count == 0) return;
_logger.LogInformation("[Kavita+ Data Refresh] Started Refreshing {Count} series data from Kavita+", ids.Count);
@ -133,6 +133,7 @@ public class ExternalMetadataService : IExternalMetadataService
/// </summary>
/// <param name="seriesId"></param>
/// <param name="libraryType"></param>
/// <returns>If a successful match was made</returns>
public async Task<bool> FetchSeriesMetadata(int seriesId, LibraryType libraryType)
{
if (!IsPlusEligible(libraryType)) return false;
@ -150,8 +151,7 @@ public class ExternalMetadataService : IExternalMetadataService
_logger.LogDebug("Prefetching Kavita+ data for Series {SeriesId}", seriesId);
// Prefetch SeriesDetail data
await GetSeriesDetailPlus(seriesId, libraryType);
return true;
return await GetSeriesDetailPlus(seriesId, libraryType) != null;
}
public async Task<IList<MalStackDto>> GetStacksForUser(int userId)
@ -303,7 +303,7 @@ public class ExternalMetadataService : IExternalMetadataService
/// </summary>
/// <param name="seriesId"></param>
/// <param name="anilistId"></param>
public async Task FixSeriesMatch(int seriesId, int anilistId)
public async Task FixSeriesMatch(int seriesId, int anilistId, long? malId)
{
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.Library);
if (series == null) return;
@ -317,7 +317,8 @@ public class ExternalMetadataService : IExternalMetadataService
var metadata = await FetchExternalMetadataForSeries(seriesId, series.Library.Type, new PlusSeriesRequestDto()
{
AniListId = anilistId,
SeriesName = string.Empty // Required field
MalId = malId,
SeriesName = series.Name // Required field, not used since AniList/Mal Id are passed
});
if (metadata.Series == null)
@ -329,6 +330,11 @@ public class ExternalMetadataService : IExternalMetadataService
// Find all scrobble events and rewrite them to be the correct
var events = await _unitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId);
_unitOfWork.ScrobbleRepository.Remove(events);
// Find all scrobble errors and remove them
var errors = await _unitOfWork.ScrobbleRepository.GetAllScrobbleErrorsForSeries(seriesId);
_unitOfWork.ScrobbleRepository.Remove(errors);
await _unitOfWork.CommitAsync();
// Regenerate all events for the series for all users
@ -566,7 +572,7 @@ public class ExternalMetadataService : IExternalMetadataService
return false;
}
foreach (var relation in externalMetadataRelations)
foreach (var relation in externalMetadataRelations.Where(r => r.Relation != RelationKind.Parent))
{
var names = new [] {relation.SeriesName.PreferredTitle, relation.SeriesName.RomajiTitle, relation.SeriesName.EnglishTitle, relation.SeriesName.NativeTitle};
var relatedSeries = await _unitOfWork.SeriesRepository.GetSeriesByAnyName(

View file

@ -930,6 +930,7 @@ public class ScrobblingService : IScrobblingService
if (await _unitOfWork.ExternalSeriesMetadataRepository.IsBlacklistedSeries(evt.SeriesId))
{
_logger.LogInformation("Series {SeriesName} ({SeriesId}) can't be matched and thus cannot scrobble this event", evt.Series.Name, evt.SeriesId);
_unitOfWork.ScrobbleRepository.Attach(new ScrobbleError()
{
Comment = UnknownSeriesErrorMessage,
@ -942,6 +943,7 @@ public class ScrobblingService : IScrobblingService
evt.ProcessDateUtc = DateTime.UtcNow;
_unitOfWork.ScrobbleRepository.Update(evt);
await _unitOfWork.CommitAsync();
return 0;
}

View file

@ -9,6 +9,7 @@ using API.DTOs.Theme;
using API.Entities;
using API.Entities.Enums.Theme;
using API.Extensions;
using API.Services.Tasks.Scanner.Parser;
using API.SignalR;
using Flurl.Http;
using HtmlAgilityPack;
@ -192,7 +193,8 @@ public class ThemeService : IThemeService
private static List<string> GetPreviewUrls(IEnumerable<GitHubContent> themeContents)
{
return themeContents.Where(c => c.Name.ToLower().EndsWith(".jpg") || c.Name.ToLower().EndsWith(".png") )
return themeContents
.Where(c => Parser.IsImage(c.Name) )
.Select(p => p.DownloadUrl)
.ToList();
}

View file

@ -3,5 +3,5 @@
"Port": 5000,
"IpAddresses": "",
"BaseUrl": "/",
"Cache": 50
"Cache": 75
}