mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-03 13:44:31 -04:00
Polish 5 (#3579)
This commit is contained in:
parent
78a98d0d18
commit
5af851af08
8
.github/workflows/openapi-gen.yml
vendored
8
.github/workflows/openapi-gen.yml
vendored
@ -21,6 +21,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.REPO_GHA_PAT }}
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
@ -59,7 +60,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git config --local user.email "action@github.com"
|
git config --local user.email "action@github.com"
|
||||||
git config --local user.name "GitHub Action"
|
git config --local user.name "GitHub Action"
|
||||||
|
|
||||||
|
# Pull latest changes with rebase to avoid merge commits
|
||||||
|
git pull --rebase origin develop
|
||||||
|
|
||||||
|
# Commit and push
|
||||||
git commit -m "Update OpenAPI documentation" openapi.json
|
git commit -m "Update OpenAPI documentation" openapi.json
|
||||||
git push
|
git push
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.REPO_GHA_PAT }}
|
GITHUB_TOKEN: ${{ secrets.REPO_GHA_PAT }}
|
@ -298,38 +298,38 @@ public class ScannerServiceTests : AbstractDbTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ScanLibrary_PublishersInheritFromChapters()
|
public async Task ScanLibrary_PublishersInheritFromChapters()
|
||||||
{
|
|
||||||
const string testcase = "Flat Special - Manga.json";
|
|
||||||
|
|
||||||
var infos = new Dictionary<string, ComicInfo>();
|
|
||||||
infos.Add("Uzaki-chan Wants to Hang Out! v01 (2019) (Digital) (danke-Empire).cbz", new ComicInfo()
|
|
||||||
{
|
{
|
||||||
Publisher = "Correct Publisher"
|
const string testcase = "Flat Special - Manga.json";
|
||||||
});
|
|
||||||
infos.Add("Uzaki-chan Wants to Hang Out! - 2022 New Years Special SP01.cbz", new ComicInfo()
|
|
||||||
{
|
|
||||||
Publisher = "Special Publisher"
|
|
||||||
});
|
|
||||||
infos.Add("Uzaki-chan Wants to Hang Out! - Ch. 103 - Kouhai and Control.cbz", new ComicInfo()
|
|
||||||
{
|
|
||||||
Publisher = "Chapter Publisher"
|
|
||||||
});
|
|
||||||
|
|
||||||
var library = await _scannerHelper.GenerateScannerData(testcase, infos);
|
var infos = new Dictionary<string, ComicInfo>();
|
||||||
|
infos.Add("Uzaki-chan Wants to Hang Out! v01 (2019) (Digital) (danke-Empire).cbz", new ComicInfo()
|
||||||
|
{
|
||||||
|
Publisher = "Correct Publisher"
|
||||||
|
});
|
||||||
|
infos.Add("Uzaki-chan Wants to Hang Out! - 2022 New Years Special SP01.cbz", new ComicInfo()
|
||||||
|
{
|
||||||
|
Publisher = "Special Publisher"
|
||||||
|
});
|
||||||
|
infos.Add("Uzaki-chan Wants to Hang Out! - Ch. 103 - Kouhai and Control.cbz", new ComicInfo()
|
||||||
|
{
|
||||||
|
Publisher = "Chapter Publisher"
|
||||||
|
});
|
||||||
|
|
||||||
|
var library = await _scannerHelper.GenerateScannerData(testcase, infos);
|
||||||
|
|
||||||
|
|
||||||
var scanner = _scannerHelper.CreateServices();
|
var scanner = _scannerHelper.CreateServices();
|
||||||
await scanner.ScanLibrary(library.Id);
|
await scanner.ScanLibrary(library.Id);
|
||||||
var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series);
|
var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series);
|
||||||
|
|
||||||
Assert.NotNull(postLib);
|
Assert.NotNull(postLib);
|
||||||
Assert.Single(postLib.Series);
|
Assert.Single(postLib.Series);
|
||||||
var publishers = postLib.Series.First().Metadata.People
|
var publishers = postLib.Series.First().Metadata.People
|
||||||
.Where(p => p.Role == PersonRole.Publisher);
|
.Where(p => p.Role == PersonRole.Publisher);
|
||||||
Assert.Equal(3, publishers.Count());
|
Assert.Equal(3, publishers.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -12,11 +12,6 @@
|
|||||||
<LangVersion>latestmajor</LangVersion>
|
<LangVersion>latestmajor</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Moved to GA -->
|
|
||||||
<!-- <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>-->
|
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
<DebugSymbols>false</DebugSymbols>
|
<DebugSymbols>false</DebugSymbols>
|
||||||
|
@ -7,6 +7,7 @@ using API.Entities.Enums;
|
|||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
using API.Services.Plus;
|
using API.Services.Plus;
|
||||||
|
using EasyCaching.Core;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -21,7 +22,8 @@ public class LicenseController(
|
|||||||
ILogger<LicenseController> logger,
|
ILogger<LicenseController> logger,
|
||||||
ILicenseService licenseService,
|
ILicenseService licenseService,
|
||||||
ILocalizationService localizationService,
|
ILocalizationService localizationService,
|
||||||
ITaskScheduler taskScheduler)
|
ITaskScheduler taskScheduler,
|
||||||
|
IEasyCachingProviderFactory cachingProviderFactory)
|
||||||
: BaseApiController
|
: BaseApiController
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -32,8 +34,13 @@ public class LicenseController(
|
|||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.LicenseCache)]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.LicenseCache)]
|
||||||
public async Task<ActionResult<bool>> HasValidLicense(bool forceCheck = false)
|
public async Task<ActionResult<bool>> HasValidLicense(bool forceCheck = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
var result = await licenseService.HasActiveLicense(forceCheck);
|
var result = await licenseService.HasActiveLicense(forceCheck);
|
||||||
if (result)
|
|
||||||
|
var licenseInfoProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.License);
|
||||||
|
var cacheValue = await licenseInfoProvider.GetAsync<bool>(LicenseService.CacheKey);
|
||||||
|
|
||||||
|
if (result && !cacheValue.IsNull && !cacheValue.Value)
|
||||||
{
|
{
|
||||||
await taskScheduler.ScheduleKavitaPlusTasks();
|
await taskScheduler.ScheduleKavitaPlusTasks();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Entities.Interfaces;
|
using API.Entities.Interfaces;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -101,4 +102,24 @@ public class SeriesMetadata : IHasConcurrencyToken
|
|||||||
{
|
{
|
||||||
RowVersion++;
|
RowVersion++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Any People in this Role present
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="role"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool AnyOfRole(PersonRole role)
|
||||||
|
{
|
||||||
|
return People.Any(p => p.Role == role);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Are all instances of the role from Kavita+
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="role"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool AllKavitaPlus(PersonRole role)
|
||||||
|
{
|
||||||
|
return People.Where(p => p.Role == role).All(p => p.KavitaPlusConnection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,7 +295,16 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||||||
if (data == null) return _defaultReturn;
|
if (data == null) return _defaultReturn;
|
||||||
|
|
||||||
// Get from Kavita+ API the Full Series metadata with rec/rev and cache to ExternalMetadata tables
|
// Get from Kavita+ API the Full Series metadata with rec/rev and cache to ExternalMetadata tables
|
||||||
return await FetchExternalMetadataForSeries(seriesId, libraryType, data);
|
try
|
||||||
|
{
|
||||||
|
return await FetchExternalMetadataForSeries(seriesId, libraryType, data);
|
||||||
|
}
|
||||||
|
catch (KavitaException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Rate limit hit fetching metadata");
|
||||||
|
// This can happen when we hit rate limit
|
||||||
|
return _defaultReturn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -314,38 +323,49 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||||||
_unitOfWork.SeriesRepository.Update(series);
|
_unitOfWork.SeriesRepository.Update(series);
|
||||||
|
|
||||||
// Refetch metadata with a Direct lookup
|
// Refetch metadata with a Direct lookup
|
||||||
var metadata = await FetchExternalMetadataForSeries(seriesId, series.Library.Type, new PlusSeriesRequestDto()
|
try
|
||||||
{
|
{
|
||||||
AniListId = anilistId,
|
var metadata = await FetchExternalMetadataForSeries(seriesId, series.Library.Type,
|
||||||
MalId = malId,
|
new PlusSeriesRequestDto()
|
||||||
SeriesName = series.Name // Required field, not used since AniList/Mal Id are passed
|
{
|
||||||
});
|
AniListId = anilistId,
|
||||||
|
MalId = malId,
|
||||||
|
SeriesName = series.Name // Required field, not used since AniList/Mal Id are passed
|
||||||
|
});
|
||||||
|
|
||||||
if (metadata.Series == null)
|
if (metadata.Series == null)
|
||||||
{
|
{
|
||||||
_logger.LogError("Unable to Match {SeriesName} with Kavita+ Series AniList Id: {AniListId}", series.Name, anilistId);
|
_logger.LogError("Unable to Match {SeriesName} with Kavita+ Series AniList Id: {AniListId}",
|
||||||
return;
|
series.Name, anilistId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
BackgroundJob.Enqueue(() => _scrobblingService.CreateEventsFromExistingHistoryForSeries(seriesId));
|
||||||
|
|
||||||
|
// Name can be null on Series even with a direct match
|
||||||
|
_logger.LogInformation("Matched {SeriesName} with Kavita+ Series {MatchSeriesName}", series.Name,
|
||||||
|
metadata.Series.Name);
|
||||||
|
}
|
||||||
|
catch (KavitaException ex)
|
||||||
|
{
|
||||||
|
// We can't rethrow because Fix match is done in a background thread and Hangfire will requeue multiple times
|
||||||
|
_logger.LogInformation(ex, "Rate limit hit for matching {SeriesName} with Kavita+", series.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
BackgroundJob.Enqueue(() => _scrobblingService.CreateEventsFromExistingHistoryForSeries(seriesId));
|
|
||||||
|
|
||||||
// Name can be null on Series even with a direct match
|
|
||||||
_logger.LogInformation("Matched {SeriesName} with Kavita+ Series {MatchSeriesName}", series.Name, metadata.Series.Name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a series to Dont Match and removes all previously cached
|
/// Sets a series to Don't Match and removes all previously cached
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="seriesId"></param>
|
/// <param name="seriesId"></param>
|
||||||
public async Task UpdateSeriesDontMatch(int seriesId, bool dontMatch)
|
public async Task UpdateSeriesDontMatch(int seriesId, bool dontMatch)
|
||||||
@ -383,7 +403,10 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||||||
{
|
{
|
||||||
|
|
||||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.Library);
|
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.Library);
|
||||||
if (series == null) return _defaultReturn;
|
if (series == null)
|
||||||
|
{
|
||||||
|
return _defaultReturn;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -417,7 +440,7 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||||||
|
|
||||||
|
|
||||||
// Recommendations
|
// Recommendations
|
||||||
externalSeriesMetadata.ExternalRecommendations ??= new List<ExternalRecommendation>();
|
externalSeriesMetadata.ExternalRecommendations ??= [];
|
||||||
var recs = await ProcessRecommendations(libraryType, result.Recommendations, externalSeriesMetadata);
|
var recs = await ProcessRecommendations(libraryType, result.Recommendations, externalSeriesMetadata);
|
||||||
|
|
||||||
var extRatings = externalSeriesMetadata.ExternalRatings
|
var extRatings = externalSeriesMetadata.ExternalRatings
|
||||||
@ -437,11 +460,19 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||||||
{
|
{
|
||||||
externalSeriesMetadata.Series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
|
externalSeriesMetadata.Series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
|
||||||
|
|
||||||
madeMetadataModification = await WriteExternalMetadataToSeries(result.Series, seriesId);
|
try
|
||||||
if (madeMetadataModification)
|
|
||||||
{
|
{
|
||||||
_unitOfWork.SeriesRepository.Update(series);
|
madeMetadataModification = await WriteExternalMetadataToSeries(result.Series, seriesId);
|
||||||
|
if (madeMetadataModification)
|
||||||
|
{
|
||||||
|
_unitOfWork.SeriesRepository.Update(series);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "There was an exception when trying to write Series metadata from Kavita+");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteExternalMetadataToSeries will commit but not always
|
// WriteExternalMetadataToSeries will commit but not always
|
||||||
@ -466,13 +497,27 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||||||
}
|
}
|
||||||
catch (FlurlHttpException ex)
|
catch (FlurlHttpException ex)
|
||||||
{
|
{
|
||||||
|
var errorMessage = await ex.GetResponseStringAsync();
|
||||||
|
// Trim quotes if the response is a JSON string
|
||||||
|
errorMessage = errorMessage.Trim('"');
|
||||||
|
|
||||||
if (ex.StatusCode == 500)
|
if (ex.StatusCode == 500)
|
||||||
{
|
{
|
||||||
return _defaultReturn;
|
return _defaultReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ex.StatusCode == 400 && errorMessage.Contains("Too many Requests"))
|
||||||
|
{
|
||||||
|
throw new KavitaException("Too many requests, slow down");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
if (ex.Message.Contains("Too Many Requests"))
|
||||||
|
{
|
||||||
|
throw new KavitaException("Too many requests, slow down");
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogError(ex, "Unable to fetch external series metadata from Kavita+");
|
_logger.LogError(ex, "Unable to fetch external series metadata from Kavita+");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1079,10 +1124,18 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||||||
var aniListId = ScrobblingService.ExtractId<int?>(staff.Url, ScrobblingService.AniListStaffWebsite);
|
var aniListId = ScrobblingService.ExtractId<int?>(staff.Url, ScrobblingService.AniListStaffWebsite);
|
||||||
if (aniListId is null or <= 0) continue;
|
if (aniListId is null or <= 0) continue;
|
||||||
var person = await _unitOfWork.PersonRepository.GetPersonByAniListId(aniListId.Value);
|
var person = await _unitOfWork.PersonRepository.GetPersonByAniListId(aniListId.Value);
|
||||||
if (person != null && !string.IsNullOrEmpty(staff.ImageUrl) && string.IsNullOrEmpty(person.CoverImage))
|
if (person == null || string.IsNullOrEmpty(staff.ImageUrl) ||
|
||||||
|
!string.IsNullOrEmpty(person.CoverImage) || staff.ImageUrl.EndsWith("default.jpg")) continue;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
await _coverDbService.SetPersonCoverByUrl(person, staff.ImageUrl, false, true);
|
await _coverDbService.SetPersonCoverByUrl(person, staff.ImageUrl, false, true);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "There was an exception saving cover image for Person {PersonName} ({PersonId})", person.Name, person.Id);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,10 @@ public class LicenseService(
|
|||||||
{
|
{
|
||||||
private readonly TimeSpan _licenseCacheTimeout = TimeSpan.FromHours(8);
|
private readonly TimeSpan _licenseCacheTimeout = TimeSpan.FromHours(8);
|
||||||
public const string Cron = "0 */9 * * *";
|
public const string Cron = "0 */9 * * *";
|
||||||
private const string CacheKey = "license";
|
/// <summary>
|
||||||
|
/// Cache key for if license is valid or not
|
||||||
|
/// </summary>
|
||||||
|
public const string CacheKey = "license";
|
||||||
private const string LicenseInfoCacheKey = "license-info";
|
private const string LicenseInfoCacheKey = "license-info";
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ using API.SignalR;
|
|||||||
using Flurl.Http;
|
using Flurl.Http;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Kavita.Common;
|
using Kavita.Common;
|
||||||
using Kavita.Common.EnvironmentInfo;
|
|
||||||
using Kavita.Common.Helpers;
|
using Kavita.Common.Helpers;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -77,7 +76,7 @@ public class ScrobblingService : IScrobblingService
|
|||||||
public const string AniListCharacterWebsite = "https://anilist.co/character/";
|
public const string AniListCharacterWebsite = "https://anilist.co/character/";
|
||||||
|
|
||||||
|
|
||||||
private static readonly IDictionary<string, int> WeblinkExtractionMap = new Dictionary<string, int>()
|
private static readonly Dictionary<string, int> WeblinkExtractionMap = new Dictionary<string, int>()
|
||||||
{
|
{
|
||||||
{AniListWeblinkWebsite, 0},
|
{AniListWeblinkWebsite, 0},
|
||||||
{MalWeblinkWebsite, 0},
|
{MalWeblinkWebsite, 0},
|
||||||
@ -89,18 +88,14 @@ public class ScrobblingService : IScrobblingService
|
|||||||
|
|
||||||
private const int ScrobbleSleepTime = 1000; // We can likely tie this to AniList's 90 rate / min ((60 * 1000) / 90)
|
private const int ScrobbleSleepTime = 1000; // We can likely tie this to AniList's 90 rate / min ((60 * 1000) / 90)
|
||||||
|
|
||||||
private static readonly IList<ScrobbleProvider> BookProviders = new List<ScrobbleProvider>()
|
private static readonly IList<ScrobbleProvider> BookProviders = [];
|
||||||
{
|
private static readonly IList<ScrobbleProvider> LightNovelProviders =
|
||||||
};
|
[
|
||||||
private static readonly IList<ScrobbleProvider> LightNovelProviders = new List<ScrobbleProvider>()
|
|
||||||
{
|
|
||||||
ScrobbleProvider.AniList
|
ScrobbleProvider.AniList
|
||||||
};
|
];
|
||||||
private static readonly IList<ScrobbleProvider> ComicProviders = new List<ScrobbleProvider>();
|
private static readonly IList<ScrobbleProvider> ComicProviders = [];
|
||||||
private static readonly IList<ScrobbleProvider> MangaProviders = new List<ScrobbleProvider>()
|
private static readonly IList<ScrobbleProvider> MangaProviders = (List<ScrobbleProvider>)
|
||||||
{
|
[ScrobbleProvider.AniList];
|
||||||
ScrobbleProvider.AniList
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
private const string UnknownSeriesErrorMessage = "Series cannot be matched for Scrobbling";
|
private const string UnknownSeriesErrorMessage = "Series cannot be matched for Scrobbling";
|
||||||
@ -532,11 +527,10 @@ public class ScrobblingService : IScrobblingService
|
|||||||
{
|
{
|
||||||
// Create a new ExternalMetadata entry to indicate that this is not matchable
|
// Create a new ExternalMetadata entry to indicate that this is not matchable
|
||||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(evt.SeriesId, SeriesIncludes.ExternalMetadata);
|
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(evt.SeriesId, SeriesIncludes.ExternalMetadata);
|
||||||
if (series.ExternalSeriesMetadata == null)
|
if (series == null) return 0;
|
||||||
{
|
|
||||||
series.ExternalSeriesMetadata = new ExternalSeriesMetadata() {SeriesId = evt.SeriesId};
|
series.ExternalSeriesMetadata ??= new ExternalSeriesMetadata() {SeriesId = evt.SeriesId};
|
||||||
}
|
series.IsBlacklisted = true;
|
||||||
series!.IsBlacklisted = true;
|
|
||||||
_unitOfWork.SeriesRepository.Update(series);
|
_unitOfWork.SeriesRepository.Update(series);
|
||||||
|
|
||||||
_unitOfWork.ScrobbleRepository.Attach(new ScrobbleError()
|
_unitOfWork.ScrobbleRepository.Attach(new ScrobbleError()
|
||||||
@ -824,6 +818,7 @@ public class ScrobblingService : IScrobblingService
|
|||||||
readEvt.AppUser.Id);
|
readEvt.AppUser.Id);
|
||||||
_unitOfWork.ScrobbleRepository.Update(readEvt);
|
_unitOfWork.ScrobbleRepository.Update(readEvt);
|
||||||
}
|
}
|
||||||
|
|
||||||
progressCounter = await ProcessEvents(readEvents, userRateLimits, usersToScrobble.Count, progressCounter, totalProgress, async evt => new ScrobbleDto()
|
progressCounter = await ProcessEvents(readEvents, userRateLimits, usersToScrobble.Count, progressCounter, totalProgress, async evt => new ScrobbleDto()
|
||||||
{
|
{
|
||||||
Format = evt.Format,
|
Format = evt.Format,
|
||||||
@ -888,9 +883,9 @@ public class ScrobblingService : IScrobblingService
|
|||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (FlurlHttpException)
|
catch (FlurlHttpException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError("Kavita+ API or a Scrobble service may be experiencing an outage. Stopping sending data");
|
_logger.LogError(ex, "Kavita+ API or a Scrobble service may be experiencing an outage. Stopping sending data");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -986,7 +981,7 @@ public class ScrobblingService : IScrobblingService
|
|||||||
{
|
{
|
||||||
if (ex.Message.Contains("Access token is invalid"))
|
if (ex.Message.Contains("Access token is invalid"))
|
||||||
{
|
{
|
||||||
_logger.LogCritical("Access Token for UserId: {UserId} needs to be regenerated/renewed to continue scrobbling", evt.AppUser.Id);
|
_logger.LogCritical(ex, "Access Token for UserId: {UserId} needs to be regenerated/renewed to continue scrobbling", evt.AppUser.Id);
|
||||||
evt.IsErrored = true;
|
evt.IsErrored = true;
|
||||||
evt.ErrorDetails = AccessTokenErrorMessage;
|
evt.ErrorDetails = AccessTokenErrorMessage;
|
||||||
_unitOfWork.ScrobbleRepository.Update(evt);
|
_unitOfWork.ScrobbleRepository.Update(evt);
|
||||||
@ -1106,7 +1101,7 @@ public class ScrobblingService : IScrobblingService
|
|||||||
return null; // Unsupported website
|
return null; // Unsupported website
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id == null)
|
if (Equals(id, default(T)))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(id), "ID cannot be null.");
|
throw new ArgumentNullException(nameof(id), "ID cannot be null.");
|
||||||
}
|
}
|
||||||
@ -1140,7 +1135,7 @@ public class ScrobblingService : IScrobblingService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("User {UserName} had an issue figuring out rate: {Message}", user.UserName, ex.Message);
|
_logger.LogInformation(ex, "User {UserName} had an issue figuring out rate: {Message}", user.UserName, ex.Message);
|
||||||
userRateLimits.Add(user.Id, 0);
|
userRateLimits.Add(user.Id, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,84 +331,123 @@ public class ProcessSeries : IProcessSeries
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region PeopleAndTagsAndGenres
|
#region PeopleAndTagsAndGenres
|
||||||
if (!series.Metadata.WriterLocked)
|
if (!series.Metadata.WriterLocked)
|
||||||
{
|
{
|
||||||
var personSw = Stopwatch.StartNew();
|
var personSw = Stopwatch.StartNew();
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Writer)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Writer)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Writer);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Writer))
|
||||||
_logger.LogTrace("[TIME] Kavita took {Time} ms to process writer on Series: {File} for {Count} people", personSw.ElapsedMilliseconds, series.Name, chapterPeople.Count);
|
{
|
||||||
}
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Writer);
|
||||||
|
}
|
||||||
|
_logger.LogTrace("[TIME] Kavita took {Time} ms to process writer on Series: {File} for {Count} people", personSw.ElapsedMilliseconds, series.Name, chapterPeople.Count);
|
||||||
|
}
|
||||||
|
|
||||||
if (!series.Metadata.ColoristLocked)
|
if (!series.Metadata.ColoristLocked)
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Colorist)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Colorist)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Colorist);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Colorist))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Colorist);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.PublisherLocked)
|
if (!series.Metadata.PublisherLocked)
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Publisher)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Publisher)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Publisher);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Publisher))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Publisher);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.CoverArtistLocked)
|
if (!series.Metadata.CoverArtistLocked)
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.CoverArtist)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.CoverArtist)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.CoverArtist);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.CoverArtist))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.CoverArtist);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.CharacterLocked)
|
if (!series.Metadata.CharacterLocked)
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Character)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Character)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Character);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Character))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Character);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.EditorLocked)
|
if (!series.Metadata.EditorLocked)
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Editor)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Editor)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Editor);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Editor))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Editor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.InkerLocked)
|
if (!series.Metadata.InkerLocked)
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Inker)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Inker)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Inker);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Inker))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Inker);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.ImprintLocked)
|
if (!series.Metadata.ImprintLocked)
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Imprint)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Imprint)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Imprint);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Imprint))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Imprint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.TeamLocked)
|
if (!series.Metadata.TeamLocked)
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Team)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Team)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Team);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Team))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Team);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.LocationLocked)
|
if (!series.Metadata.LocationLocked && !series.Metadata.AllKavitaPlus(PersonRole.Location))
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Location)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Location)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Location);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Location))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Location);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.LettererLocked)
|
if (!series.Metadata.LettererLocked && !series.Metadata.AllKavitaPlus(PersonRole.Letterer))
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Letterer)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Letterer)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Letterer);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Location))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Letterer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.PencillerLocked)
|
if (!series.Metadata.PencillerLocked && !series.Metadata.AllKavitaPlus(PersonRole.Penciller))
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Penciller)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Penciller)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Penciller);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Penciller))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Penciller);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.Metadata.TranslatorLocked)
|
if (!series.Metadata.TranslatorLocked && !series.Metadata.AllKavitaPlus(PersonRole.Translator))
|
||||||
{
|
{
|
||||||
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Translator)).ToList();
|
var chapterPeople = chapters.SelectMany(c => c.People.Where(p => p.Role == PersonRole.Translator)).ToList();
|
||||||
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Translator);
|
if (ShouldUpdatePeopleForRole(series, chapterPeople, PersonRole.Translator))
|
||||||
|
{
|
||||||
|
await UpdateSeriesMetadataPeople(series.Metadata, series.Metadata.People, chapterPeople, PersonRole.Translator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -428,6 +467,34 @@ public class ProcessSeries : IProcessSeries
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure that we don't overwrite Person metadata when all metadata is coming from Kavita+ metadata match functionality
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="series"></param>
|
||||||
|
/// <param name="chapterPeople"></param>
|
||||||
|
/// <param name="role"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static bool ShouldUpdatePeopleForRole(Series series, List<ChapterPeople> chapterPeople, PersonRole role)
|
||||||
|
{
|
||||||
|
if (chapterPeople.Count == 0) return false;
|
||||||
|
|
||||||
|
// If metadata already has this role, but all entries are from KavitaPlus, we should retain them
|
||||||
|
if (series.Metadata.AnyOfRole(role))
|
||||||
|
{
|
||||||
|
var existingPeople = series.Metadata.People.Where(p => p.Role == role);
|
||||||
|
|
||||||
|
// If all existing people are KavitaPlus but new chapter people exist, we should still update
|
||||||
|
if (existingPeople.All(p => p.KavitaPlusConnection))
|
||||||
|
{
|
||||||
|
return false; // Ensure we don't remove KavitaPlus people
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // Default case: metadata exists, and it's okay to update
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task UpdateCollectionTags(Series series, Chapter firstChapter)
|
private async Task UpdateCollectionTags(Series series, Chapter firstChapter)
|
||||||
{
|
{
|
||||||
// Get the default admin to associate these tags to
|
// Get the default admin to associate these tags to
|
||||||
@ -553,8 +620,8 @@ public class ProcessSeries : IProcessSeries
|
|||||||
.Where(v => v.MaxNumber.IsNot(Parser.Parser.SpecialVolumeNumber))
|
.Where(v => v.MaxNumber.IsNot(Parser.Parser.SpecialVolumeNumber))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var maxVolume = (int) (nonSpecialVolumes.Any() ? nonSpecialVolumes.Max(v => v.MaxNumber) : 0);
|
var maxVolume = (int)(nonSpecialVolumes.Any() ? nonSpecialVolumes.Max(v => v.MaxNumber) : 0);
|
||||||
var maxChapter = (int) chapters.Max(c => c.MaxNumber);
|
var maxChapter = (int)chapters.Max(c => c.MaxNumber);
|
||||||
|
|
||||||
// Single books usually don't have a number in their Range (filename)
|
// Single books usually don't have a number in their Range (filename)
|
||||||
if (series.Format == MangaFormat.Epub || series.Format == MangaFormat.Pdf && chapters.Count == 1)
|
if (series.Format == MangaFormat.Epub || series.Format == MangaFormat.Pdf && chapters.Count == 1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user