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,