From f30e3b17d6a2950c444881bb9e4414b79e53f611 Mon Sep 17 00:00:00 2001 From: Tyler Kenney Date: Tue, 20 May 2025 09:35:39 -0400 Subject: [PATCH] Added API benchmark, hash device_id, handled null cases in GET/PUT --- API.Benchmark/API.Benchmark.csproj | 5 +++ .../Data}/AesopsFables.epub | Bin API.Benchmark/KoreaderHashBenchmark.cs | 41 ++++++++++++++++++ API/Controllers/KoreaderController.cs | 17 ++++++-- .../Builders/KoreaderBookDtoBuilder.cs | 6 ++- API/Services/KoreaderService.cs | 19 +++++--- UI/Web/src/environments/environment.ts | 2 +- 7 files changed, 79 insertions(+), 11 deletions(-) rename {API.Tests/Data/Aesops Fables => API.Benchmark/Data}/AesopsFables.epub (100%) create mode 100644 API.Benchmark/KoreaderHashBenchmark.cs diff --git a/API.Benchmark/API.Benchmark.csproj b/API.Benchmark/API.Benchmark.csproj index 38ec425fe..d6fd4eb9f 100644 --- a/API.Benchmark/API.Benchmark.csproj +++ b/API.Benchmark/API.Benchmark.csproj @@ -26,5 +26,10 @@ Always + + + PreserveNewest + + diff --git a/API.Tests/Data/Aesops Fables/AesopsFables.epub b/API.Benchmark/Data/AesopsFables.epub similarity index 100% rename from API.Tests/Data/Aesops Fables/AesopsFables.epub rename to API.Benchmark/Data/AesopsFables.epub diff --git a/API.Benchmark/KoreaderHashBenchmark.cs b/API.Benchmark/KoreaderHashBenchmark.cs new file mode 100644 index 000000000..c0abfd2ad --- /dev/null +++ b/API.Benchmark/KoreaderHashBenchmark.cs @@ -0,0 +1,41 @@ +using API.Helpers.Builders; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; +using System; +using API.Entities.Enums; + +namespace API.Benchmark +{ + [StopOnFirstError] + [MemoryDiagnoser] + [RankColumn] + [Orderer(SummaryOrderPolicy.FastestToSlowest)] + [SimpleJob(launchCount: 1, warmupCount: 5, invocationCount: 20)] + public class KoreaderHashBenchmark + { + private const string sourceEpub = "./Data/AesopsFables.epub"; + + [Benchmark(Baseline = true)] + public void TestBuildManga_baseline() + { + var file = new MangaFileBuilder(sourceEpub, MangaFormat.Epub) + .Build(); + if (file == null) + { + throw new Exception("Failed to build manga file"); + } + } + + [Benchmark] + public void TestBuildManga_withHash() + { + var file = new MangaFileBuilder(sourceEpub, MangaFormat.Epub) + .WithHash() + .Build(); + if (file == null) + { + throw new Exception("Failed to build manga file"); + } + } + } +} diff --git a/API/Controllers/KoreaderController.cs b/API/Controllers/KoreaderController.cs index ec87f83f1..8ad9ca0e5 100644 --- a/API/Controllers/KoreaderController.cs +++ b/API/Controllers/KoreaderController.cs @@ -78,11 +78,20 @@ public class KoreaderController : BaseApiController [HttpGet("{apiKey}/syncs/progress/{ebookHash}")] public async Task> GetProgress(string apiKey, string ebookHash) { - var userId = await GetUserId(apiKey); - var response = await _koreaderService.GetProgress(ebookHash, userId); - _logger.LogDebug("Koreader response progress: {Progress}", response.Progress); + try + { + var userId = await GetUserId(apiKey); + var response = await _koreaderService.GetProgress(ebookHash, userId); + _logger.LogDebug("Koreader response progress: {Progress}", response.Progress); - return Ok(response); + return Ok(response); + } + catch (KavitaException ex) + { + return BadRequest(ex.Message); + } + + return BadRequest(); } private async Task GetUserId(string apiKey) diff --git a/API/Helpers/Builders/KoreaderBookDtoBuilder.cs b/API/Helpers/Builders/KoreaderBookDtoBuilder.cs index 64c7adf49..fd701b5e2 100644 --- a/API/Helpers/Builders/KoreaderBookDtoBuilder.cs +++ b/API/Helpers/Builders/KoreaderBookDtoBuilder.cs @@ -1,3 +1,5 @@ +using System.Security.Cryptography; +using System.Text; using API.DTOs.Koreader; namespace API.Helpers.Builders; @@ -36,7 +38,9 @@ public class KoreaderBookDtoBuilder : IEntityBuilder public KoreaderBookDtoBuilder WithDeviceId(string installId, int userId) { - _dto.Device_id = installId; + using var sha256 = SHA256.Create(); + var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(installId + userId)); + _dto.Device_id = hash.ToString(); return this; } } diff --git a/API/Services/KoreaderService.cs b/API/Services/KoreaderService.cs index 1ed5ebbe5..fbb51e2ac 100644 --- a/API/Services/KoreaderService.cs +++ b/API/Services/KoreaderService.cs @@ -4,6 +4,7 @@ using API.DTOs.Koreader; using API.DTOs.Progress; using API.Helpers; using API.Helpers.Builders; +using Kavita.Common; using Microsoft.Extensions.Logging; namespace API.Services; @@ -20,12 +21,14 @@ public class KoreaderService : IKoreaderService { private readonly IReaderService _readerService; private readonly IUnitOfWork _unitOfWork; + private readonly ILocalizationService _localizationService; private readonly ILogger _logger; - public KoreaderService(IReaderService readerService, IUnitOfWork unitOfWork, ILogger logger) + public KoreaderService(IReaderService readerService, IUnitOfWork unitOfWork, ILocalizationService localizationService, ILogger logger) { _readerService = readerService; _unitOfWork = unitOfWork; + _localizationService = localizationService; _logger = logger; } @@ -43,10 +46,17 @@ public class KoreaderService : IKoreaderService var userProgressDto = await _unitOfWork.AppUserProgressRepository.GetUserProgressDtoAsync(file.ChapterId, userId); if (userProgressDto == null) { - // TODO: Handle this case + var chapterDto = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(file.ChapterId); + if (chapterDto == null) return; + + var volumeDto = await _unitOfWork.VolumeRepository.GetVolumeByIdAsync(chapterDto.VolumeId); + if (volumeDto == null) return; + userProgressDto = new ProgressDto() { ChapterId = file.ChapterId, + VolumeId = chapterDto.VolumeId, + SeriesId = volumeDto.SeriesId, }; } // Update the bookScrollId if possible @@ -68,15 +78,14 @@ public class KoreaderService : IKoreaderService var file = await _unitOfWork.MangaFileRepository.GetByKoreaderHash(bookHash); - // TODO: How do we handle when file isn't found by hash? - if (file == null) return builder.Build(); + if (file == null) throw new KavitaException(await _localizationService.Translate(userId, "file-missing")); var progressDto = await _unitOfWork.AppUserProgressRepository.GetUserProgressDtoAsync(file.ChapterId, userId); var koreaderProgress = KoreaderHelper.GetKoreaderPosition(progressDto); return builder.WithProgress(koreaderProgress) .WithPercentage(progressDto?.PageNum, file.Pages) - .WithDeviceId(settingsDto.InstallId, userId) // TODO: Should we generate a hash for UserId + InstallId so that this DeviceId is unique to the user on the server? + .WithDeviceId(settingsDto.InstallId, userId) .Build(); } } diff --git a/UI/Web/src/environments/environment.ts b/UI/Web/src/environments/environment.ts index e1d9c819c..55758b792 100644 --- a/UI/Web/src/environments/environment.ts +++ b/UI/Web/src/environments/environment.ts @@ -3,7 +3,7 @@ // The list of file replacements can be found in `angular.json`. // const IP = 'localhost'; -const IP = '10.10.30.215'; +const IP = 'localhost'; export const environment = { production: false,