Misc bunch of changes (#2815)

This commit is contained in:
Joe Milazzo 2024-03-23 16:20:16 -05:00 committed by GitHub
parent 18792b7b56
commit 63c9bff32e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
81 changed files with 4567 additions and 339 deletions

View file

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
using API.DTOs;
using API.DTOs.Collection;
using API.DTOs.Recommendation;
using API.DTOs.Scrobbling;
using API.DTOs.SeriesDetail;
@ -61,6 +62,8 @@ public interface IExternalMetadataService
/// <param name="libraryType"></param>
/// <returns></returns>
Task GetNewSeriesData(int seriesId, LibraryType libraryType);
Task<IList<MalStackDto>> GetStacksForUser(int userId);
}
public class ExternalMetadataService : IExternalMetadataService
@ -70,7 +73,8 @@ public class ExternalMetadataService : IExternalMetadataService
private readonly IMapper _mapper;
private readonly ILicenseService _licenseService;
private readonly TimeSpan _externalSeriesMetadataCache = TimeSpan.FromDays(30);
public static readonly ImmutableArray<LibraryType> NonEligibleLibraryTypes = ImmutableArray.Create<LibraryType>(LibraryType.Comic, LibraryType.Book, LibraryType.Image, LibraryType.ComicVine);
public static readonly ImmutableArray<LibraryType> NonEligibleLibraryTypes = ImmutableArray.Create
(LibraryType.Comic, LibraryType.Book, LibraryType.Image, LibraryType.ComicVine);
private readonly SeriesDetailPlusDto _defaultReturn = new()
{
Recommendations = null,
@ -137,12 +141,15 @@ public class ExternalMetadataService : IExternalMetadataService
public async Task ForceKavitaPlusRefresh(int seriesId)
{
if (!await _licenseService.HasActiveLicense()) return;
// Remove from Blacklist if applicable
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeBySeriesIdAsync(seriesId);
if (!IsPlusEligible(libraryType)) return;
// Remove from Blacklist if applicable
await _unitOfWork.ExternalSeriesMetadataRepository.RemoveFromBlacklist(seriesId);
var metadata = await _unitOfWork.ExternalSeriesMetadataRepository.GetExternalSeriesMetadata(seriesId);
if (metadata == null) return;
metadata.ValidUntilUtc = DateTime.UtcNow.Subtract(_externalSeriesMetadataCache);
await _unitOfWork.CommitAsync();
}
@ -170,10 +177,50 @@ public class ExternalMetadataService : IExternalMetadataService
// Prefetch SeriesDetail data
await GetSeriesDetailPlus(seriesId, libraryType);
// TODO: Fetch Series Metadata
// TODO: Fetch Series Metadata (Summary, etc)
}
public async Task<IList<MalStackDto>> GetStacksForUser(int userId)
{
if (!await _licenseService.HasActiveLicense()) return ArraySegment<MalStackDto>.Empty;
// See if this user has Mal account on record
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
if (user == null || string.IsNullOrEmpty(user.MalUserName) || string.IsNullOrEmpty(user.MalAccessToken))
{
_logger.LogInformation("User is attempting to fetch MAL Stacks, but missing information on their account");
return ArraySegment<MalStackDto>.Empty;
}
try
{
_logger.LogDebug("Fetching Kavita+ for MAL Stacks for user {UserName}", user.MalUserName);
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
var result = await ($"{Configuration.KavitaPlusApiUrl}/api/metadata/v2/stacks?username={user.MalUserName}")
.WithHeader("Accept", "application/json")
.WithHeader("User-Agent", "Kavita")
.WithHeader("x-license-key", license)
.WithHeader("x-installId", HashUtil.ServerToken())
.WithHeader("x-kavita-version", BuildInfo.Version)
.WithHeader("Content-Type", "application/json")
.WithTimeout(TimeSpan.FromSeconds(Configuration.DefaultTimeOutSecs))
.GetJsonAsync<IList<MalStackDto>>();
if (result == null)
{
return ArraySegment<MalStackDto>.Empty;
}
return result;
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Fetching Kavita+ for MAL Stacks for user {UserName} failed", user.MalUserName);
return ArraySegment<MalStackDto>.Empty;
}
}
/// <summary>
/// Retrieves Metadata about a Recommended External Series
/// </summary>

View file

@ -94,6 +94,7 @@ public class ScrobblingService : IScrobblingService
ScrobbleProvider.AniList
};
private const string UnknownSeriesErrorMessage = "Series cannot be matched for Scrobbling";
private const string AccessTokenErrorMessage = "Access Token needs to be rotated to continue scrobbling";
@ -332,15 +333,7 @@ public class ScrobblingService : IScrobblingService
await _unitOfWork.AppUserProgressRepository.GetHighestFullyReadChapterForSeries(seriesId, userId),
Format = LibraryTypeHelper.GetFormat(series.Library.Type),
};
// NOTE: Not sure how to handle scrobbling specials or handling sending loose leaf volumes
if (evt.VolumeNumber is Parser.SpecialVolumeNumber)
{
evt.VolumeNumber = 0;
}
if (evt.VolumeNumber is Parser.DefaultChapterNumber)
{
evt.VolumeNumber = 0;
}
_unitOfWork.ScrobbleRepository.Attach(evt);
await _unitOfWork.CommitAsync();
_logger.LogDebug("Added Scrobbling Read update on {SeriesName} with Userid {UserId} ", series.Name, userId);
@ -826,6 +819,20 @@ public class ScrobblingService : IScrobblingService
try
{
var data = await createEvent(evt);
// We need to handle the encoding and changing it to the old one until we can update the API layer to handle these
// which could happen in v0.8.3
if (data.VolumeNumber is Parser.SpecialVolumeNumber)
{
data.VolumeNumber = 0;
}
if (data.VolumeNumber is Parser.DefaultChapterNumber)
{
data.VolumeNumber = 0;
}
if (data.ChapterNumber is Parser.DefaultChapterNumber)
{
data.ChapterNumber = 0;
}
userRateLimits[evt.AppUserId] = await PostScrobbleUpdate(data, license.Value, evt);
evt.IsProcessed = true;
evt.ProcessDateUtc = DateTime.UtcNow;
@ -870,6 +877,7 @@ public class ScrobblingService : IScrobblingService
}
}
private static bool DoesUserHaveProviderAndValid(ScrobbleEvent readEvent)
{
var userProviders = GetUserProviders(readEvent.AppUser);

View file

@ -9,6 +9,7 @@ using API.Comparators;
using API.Data;
using API.Data.Repositories;
using API.DTOs;
using API.DTOs.Progress;
using API.DTOs.Reader;
using API.Entities;
using API.Entities.Enums;

View file

@ -9,6 +9,7 @@ using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Extensions.QueryExtensions;
using API.Services.Plus;
using API.Services.Tasks.Scanner.Parser;
using AutoMapper;
using AutoMapper.QueryableExtensions;
@ -33,6 +34,7 @@ public interface IStatisticService
IEnumerable<StatCount<int>> GetWordsReadCountByYear(int userId = 0);
Task UpdateServerStatistics();
Task<long> TimeSpentReadingForUsersAsync(IList<int> userIds, IList<int> libraryIds);
Task<KavitaPlusMetadataBreakdownDto> GetKavitaPlusMetadataBreakdown();
}
/// <summary>
@ -531,6 +533,29 @@ public class StatisticService : IStatisticService
p.chapter.AvgHoursToRead * (p.progress.PagesRead / (1.0f * p.chapter.Pages))));
}
public async Task<KavitaPlusMetadataBreakdownDto> GetKavitaPlusMetadataBreakdown()
{
// We need to count number of Series that have an external series record
// Then count how many series are blacklisted
// Then get total count of series that are Kavita+ eligible
var plusLibraries = await _context.Library
.Where(l => !ExternalMetadataService.NonEligibleLibraryTypes.Contains(l.Type))
.Select(l => l.Id)
.ToListAsync();
var countOfBlacklisted = await _context.SeriesBlacklist.CountAsync();
var totalSeries = await _context.Series.Where(s => plusLibraries.Contains(s.LibraryId)).CountAsync();
var seriesWithMetadata = await _context.ExternalSeriesMetadata.CountAsync();
return new KavitaPlusMetadataBreakdownDto()
{
TotalSeries = totalSeries,
ErroredSeries = countOfBlacklisted,
SeriesCompleted = seriesWithMetadata
};
}
public async Task<IEnumerable<TopReadDto>> GetTopUsers(int days)
{
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).ToList();

View file

@ -9,6 +9,7 @@ using API.Entities.Enums;
using API.Extensions;
using API.Services.Tasks.Scanner.Parser;
using API.SignalR;
using ExCSS;
using Kavita.Common.Helpers;
using Microsoft.Extensions.Logging;
@ -448,22 +449,42 @@ public class ParseScannedFiles
var infos = scannedSeries[series].Where(info => info.Volumes == volume.Key).ToList();
IList<ParserInfo> chapters;
var specialTreatment = infos.TrueForAll(info => info.IsSpecial);
var hasAnySpMarker = infos.Exists(info => info.SpecialIndex > 0);
var counter = 0f;
if (specialTreatment)
if (specialTreatment && hasAnySpMarker)
{
chapters = infos
.OrderBy(info => info.SpecialIndex)
.ToList();
foreach (var chapter in chapters)
{
chapter.IssueOrder = counter;
counter++;
}
return;
}
else
chapters = infos
.OrderByNatural(info => info.Chapters)
.ToList();
// If everything is a special but we don't have any SpecialIndex, then order naturally and use 0, 1, 2
if (specialTreatment)
{
chapters = infos
.OrderByNatural(info => info.Chapters)
.ToList();
foreach (var chapter in chapters)
{
chapter.IssueOrder = counter;
counter++;
}
return;
}
var counter = 0f;
counter = 0f;
var prevIssue = string.Empty;
foreach (var chapter in chapters)
{

View file

@ -95,6 +95,11 @@ public class BasicParser(IDirectoryService directoryService, IDefaultParser imag
// Patch in other information from ComicInfo
UpdateFromComicInfo(ret);
if (ret.Volumes == Parser.LooseLeafVolume && ret.Chapters == Parser.DefaultChapter)
{
ret.IsSpecial = true;
}
// v0.8.x: Introducing a change where Specials will go in a separate Volume with a reserved number
if (ret.IsSpecial)
{

View file

@ -22,6 +22,7 @@ public class BookParser(IDirectoryService directoryService, IBookService bookSer
if (string.IsNullOrEmpty(info.ComicInfo?.Volume) && hasVolumeInTitle && (hasVolumeInSeries || string.IsNullOrEmpty(info.Series)))
{
// NOTE: I'm not sure the comment is true. I've never seen this triggered
// This is likely a light novel for which we can set series from parsed title
info.Series = Parser.ParseSeries(info.Title);
info.Volumes = Parser.ParseVolume(info.Title);
@ -30,6 +31,12 @@ public class BookParser(IDirectoryService directoryService, IBookService bookSer
{
var info2 = basicParser.Parse(filePath, rootPath, libraryRoot, LibraryType.Book, comicInfo);
info.Merge(info2);
if (type == LibraryType.LightNovel && hasVolumeInSeries && info2 != null && Parser.ParseVolume(info2.Series)
.Equals(Parser.LooseLeafVolume))
{
// Override the Series name so it groups appropriately
info.Series = info2.Series;
}
}
}