diff --git a/API/Constants/ResponseCacheProfiles.cs b/API/Constants/ResponseCacheProfiles.cs
new file mode 100644
index 000000000..050a769c7
--- /dev/null
+++ b/API/Constants/ResponseCacheProfiles.cs
@@ -0,0 +1,17 @@
+namespace API.Constants;
+
+public static class ResponseCacheProfiles
+{
+ public const string Images = "Images";
+ public const string Hour = "Hour";
+ public const string TenMinute = "10Minute";
+ public const string FiveMinute = "5Minute";
+ ///
+ /// 6 hour long cache as underlying API is expensive
+ ///
+ public const string Statistics = "Statistics";
+ ///
+ /// Instant is a very quick cache, because we can't bust based on the query params, but rather body
+ ///
+ public const string Instant = "Instant";
+}
diff --git a/API/Controllers/ImageController.cs b/API/Controllers/ImageController.cs
index 12b116cb8..cdd13882a 100644
--- a/API/Controllers/ImageController.cs
+++ b/API/Controllers/ImageController.cs
@@ -1,5 +1,6 @@
using System.IO;
using System.Threading.Tasks;
+using API.Constants;
using API.Data;
using API.Entities.Enums;
using API.Extensions;
@@ -31,7 +32,7 @@ public class ImageController : BaseApiController
///
///
[HttpGet("chapter-cover")]
- [ResponseCache(CacheProfileName = "Images")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
public async Task GetChapterCoverImage(int chapterId)
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId));
@@ -47,7 +48,7 @@ public class ImageController : BaseApiController
///
///
[HttpGet("library-cover")]
- [ResponseCache(CacheProfileName = "Images")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
public async Task GetLibraryCoverImage(int libraryId)
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.LibraryRepository.GetLibraryCoverImageAsync(libraryId));
@@ -63,7 +64,7 @@ public class ImageController : BaseApiController
///
///
[HttpGet("volume-cover")]
- [ResponseCache(CacheProfileName = "Images")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
public async Task GetVolumeCoverImage(int volumeId)
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId));
@@ -78,7 +79,7 @@ public class ImageController : BaseApiController
///
/// Id of Series
///
- [ResponseCache(CacheProfileName = "Images")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
[HttpGet("series-cover")]
public async Task GetSeriesCoverImage(int seriesId)
{
@@ -97,7 +98,7 @@ public class ImageController : BaseApiController
///
///
[HttpGet("collection-cover")]
- [ResponseCache(CacheProfileName = "Images")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
public async Task GetCollectionCoverImage(int collectionTagId)
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId));
@@ -113,7 +114,7 @@ public class ImageController : BaseApiController
///
///
[HttpGet("readinglist-cover")]
- [ResponseCache(CacheProfileName = "Images")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
public async Task GetReadingListCoverImage(int readingListId)
{
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ReadingListRepository.GetCoverImageAsync(readingListId));
@@ -132,7 +133,7 @@ public class ImageController : BaseApiController
/// API Key for user. Needed to authenticate request
///
[HttpGet("bookmark")]
- [ResponseCache(CacheProfileName = "Images")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
public async Task GetBookmarkImage(int chapterId, int pageNum, string apiKey)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
@@ -154,7 +155,7 @@ public class ImageController : BaseApiController
///
[Authorize(Policy="RequireAdminRole")]
[HttpGet("cover-upload")]
- [ResponseCache(CacheProfileName = "Images")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
public ActionResult GetCoverUploadImage(string filename)
{
if (filename.Contains("..")) return BadRequest("Invalid Filename");
diff --git a/API/Controllers/MetadataController.cs b/API/Controllers/MetadataController.cs
index b0c9b62be..9b3c5876a 100644
--- a/API/Controllers/MetadataController.cs
+++ b/API/Controllers/MetadataController.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
+using API.Constants;
using API.Data;
using API.DTOs;
using API.DTOs.Filtering;
@@ -84,7 +85,7 @@ public class MetadataController : BaseApiController
/// String separated libraryIds or null for all ratings
/// This API is cached for 1 hour, varying by libraryIds
///
- [ResponseCache(CacheProfileName = "5Minute", VaryByQueryKeys = new [] {"libraryIds"})]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.FiveMinute, VaryByQueryKeys = new [] {"libraryIds"})]
[HttpGet("age-ratings")]
public async Task>> GetAllAgeRatings(string? libraryIds)
{
@@ -107,7 +108,7 @@ public class MetadataController : BaseApiController
/// String separated libraryIds or null for all publication status
/// This API is cached for 1 hour, varying by libraryIds
///
- [ResponseCache(CacheProfileName = "5Minute", VaryByQueryKeys = new [] {"libraryIds"})]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.FiveMinute, VaryByQueryKeys = new [] {"libraryIds"})]
[HttpGet("publication-status")]
public ActionResult> GetAllPublicationStatus(string? libraryIds)
{
diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs
index c13a99079..ad7f61143 100644
--- a/API/Controllers/OPDSController.cs
+++ b/API/Controllers/OPDSController.cs
@@ -787,7 +787,7 @@ public class OpdsController : BaseApiController
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"/api/image/chapter-cover?chapterId={chapterId}"),
// We can't not include acc link in the feed, panels doesn't work with just page streaming option. We have to block download directly
accLink,
- CreatePageStreamLink(seriesId, volumeId, chapterId, mangaFile, apiKey)
+ CreatePageStreamLink(series.LibraryId,seriesId, volumeId, chapterId, mangaFile, apiKey)
},
Content = new FeedEntryContent()
{
@@ -800,7 +800,7 @@ public class OpdsController : BaseApiController
}
[HttpGet("{apiKey}/image")]
- public async Task GetPageStreamedImage(string apiKey, [FromQuery] int seriesId, [FromQuery] int volumeId,[FromQuery] int chapterId, [FromQuery] int pageNumber)
+ public async Task GetPageStreamedImage(string apiKey, [FromQuery] int libraryId, [FromQuery] int seriesId, [FromQuery] int volumeId,[FromQuery] int chapterId, [FromQuery] int pageNumber)
{
if (pageNumber < 0) return BadRequest("Page cannot be less than 0");
var chapter = await _cacheService.Ensure(chapterId);
@@ -823,7 +823,8 @@ public class OpdsController : BaseApiController
ChapterId = chapterId,
PageNum = pageNumber,
SeriesId = seriesId,
- VolumeId = volumeId
+ VolumeId = volumeId,
+ LibraryId =libraryId
}, await GetUser(apiKey));
return File(content, "image/" + format);
@@ -866,9 +867,9 @@ public class OpdsController : BaseApiController
throw new KavitaException("User does not exist");
}
- private static FeedLink CreatePageStreamLink(int seriesId, int volumeId, int chapterId, MangaFile mangaFile, string apiKey)
+ private static FeedLink CreatePageStreamLink(int libraryId, int seriesId, int volumeId, int chapterId, MangaFile mangaFile, string apiKey)
{
- var link = CreateLink(FeedLinkRelation.Stream, "image/jpeg", $"{Prefix}{apiKey}/image?seriesId={seriesId}&volumeId={volumeId}&chapterId={chapterId}&pageNumber=" + "{pageNumber}");
+ var link = CreateLink(FeedLinkRelation.Stream, "image/jpeg", $"{Prefix}{apiKey}/image?libraryId={libraryId}&seriesId={seriesId}&volumeId={volumeId}&chapterId={chapterId}&pageNumber=" + "{pageNumber}");
link.TotalPages = mangaFile.Pages;
return link;
}
diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs
index cd2001c08..cff8c24d4 100644
--- a/API/Controllers/ReaderController.cs
+++ b/API/Controllers/ReaderController.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
+using API.Constants;
using API.Data;
using API.Data.Repositories;
using API.DTOs;
@@ -56,7 +57,7 @@ public class ReaderController : BaseApiController
///
///
[HttpGet("pdf")]
- [ResponseCache(CacheProfileName = "Hour")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)]
public async Task GetPdf(int chapterId)
{
var chapter = await _cacheService.Ensure(chapterId);
@@ -90,7 +91,7 @@ public class ReaderController : BaseApiController
///
///
[HttpGet("image")]
- [ResponseCache(CacheProfileName = "Hour")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)]
[AllowAnonymous]
public async Task GetImage(int chapterId, int page)
{
@@ -122,7 +123,7 @@ public class ReaderController : BaseApiController
/// We must use api key as bookmarks could be leaked to other users via the API
///
[HttpGet("bookmark-image")]
- [ResponseCache(CacheProfileName = "Hour")]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)]
[AllowAnonymous]
public async Task GetBookmarkImage(int seriesId, string apiKey, int page)
{
diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs
index 4433ade21..c93e93fe9 100644
--- a/API/Controllers/SeriesController.cs
+++ b/API/Controllers/SeriesController.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using API.Constants;
using API.Data;
using API.Data.Repositories;
using API.DTOs;
@@ -383,7 +384,7 @@ public class SeriesController : BaseApiController
///
///
/// Do not rely on this API externally. May change without hesitation.
- [ResponseCache(CacheProfileName = "5Minute", VaryByQueryKeys = new [] {"seriesId"})]
+ [ResponseCache(CacheProfileName = ResponseCacheProfiles.FiveMinute, VaryByQueryKeys = new [] {"seriesId"})]
[HttpGet("series-detail")]
public async Task> GetSeriesDetailBreakdown(int seriesId)
{
diff --git a/API/Controllers/ServerController.cs b/API/Controllers/ServerController.cs
index 02727f686..7c921fe8f 100644
--- a/API/Controllers/ServerController.cs
+++ b/API/Controllers/ServerController.cs
@@ -35,10 +35,11 @@ public class ServerController : BaseApiController
private readonly ICleanupService _cleanupService;
private readonly IEmailService _emailService;
private readonly IBookmarkService _bookmarkService;
+ private readonly IScannerService _scannerService;
public ServerController(IHostApplicationLifetime applicationLifetime, ILogger logger,
IBackupService backupService, IArchiveService archiveService, IVersionUpdaterService versionUpdaterService, IStatsService statsService,
- ICleanupService cleanupService, IEmailService emailService, IBookmarkService bookmarkService)
+ ICleanupService cleanupService, IEmailService emailService, IBookmarkService bookmarkService, IScannerService scannerService)
{
_applicationLifetime = applicationLifetime;
_logger = logger;
@@ -49,6 +50,7 @@ public class ServerController : BaseApiController
_cleanupService = cleanupService;
_emailService = emailService;
_bookmarkService = bookmarkService;
+ _scannerService = scannerService;
}
///
@@ -85,7 +87,7 @@ public class ServerController : BaseApiController
public ActionResult CleanupWantToRead()
{
_logger.LogInformation("{UserName} is clearing running want to read cleanup from admin dashboard", User.GetUsername());
- RecurringJob.TriggerJob(API.Services.TaskScheduler.RemoveFromWantToReadTaskId);
+ RecurringJob.TriggerJob(TaskScheduler.RemoveFromWantToReadTaskId);
return Ok();
}
@@ -98,7 +100,23 @@ public class ServerController : BaseApiController
public ActionResult BackupDatabase()
{
_logger.LogInformation("{UserName} is backing up database of server from admin dashboard", User.GetUsername());
- RecurringJob.TriggerJob(API.Services.TaskScheduler.BackupTaskId);
+ RecurringJob.TriggerJob(TaskScheduler.BackupTaskId);
+ return Ok();
+ }
+
+ ///
+ /// This is a one time task that needs to be ran for v0.7 statistics to work
+ ///
+ ///
+ [HttpPost("analyze-files")]
+ public ActionResult AnalyzeFiles()
+ {
+ _logger.LogInformation("{UserName} is performing file analysis from admin dashboard", User.GetUsername());
+ if (TaskScheduler.HasAlreadyEnqueuedTask(ScannerService.Name, "AnalyzeFiles",
+ Array.Empty