Bugfix/release cleanup (#512)
* Lots of cleanup on the warnings in the solution. Deprecated IsLastWriteLessThan and made a new method HasFileBeenModifiedSince. * Added some tests for the new extension method. * Changed filter import to use correct import * Scan Series now uses Refresh Metadata for Series, rather than library one. * Fixed an issue where cover generation wasn't properly taking forced update into consideration. Removed a case of cover generation for no reason. * Fixed series downloads not triggering backend call
This commit is contained in:
parent
2a3a08de74
commit
0d2d73e8ae
33 changed files with 116 additions and 131 deletions
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ namespace API.Controllers
|
|||
{
|
||||
return await GetFirstFileDownload(files);
|
||||
}
|
||||
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
|
||||
var (fileBytes, _) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
|
||||
$"download_{User.GetUsername()}_v{volumeId}");
|
||||
return File(fileBytes, DefaultContentType, $"{series.Name} - Volume {volume.Number}.zip");
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@ namespace API.Controllers
|
|||
{
|
||||
return await GetFirstFileDownload(files);
|
||||
}
|
||||
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
|
||||
var (fileBytes, _) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
|
||||
$"download_{User.GetUsername()}_c{chapterId}");
|
||||
return File(fileBytes, DefaultContentType, $"{series.Name} - Chapter {chapter.Number}.zip");
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ namespace API.Controllers
|
|||
{
|
||||
return await GetFirstFileDownload(files);
|
||||
}
|
||||
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
|
||||
var (fileBytes, _) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
|
||||
$"download_{User.GetUsername()}_s{seriesId}");
|
||||
return File(fileBytes, DefaultContentType, $"{series.Name}.zip");
|
||||
}
|
||||
|
|
@ -194,11 +194,11 @@ namespace API.Controllers
|
|||
var files = _directoryService.GetFilesWithExtension(chapterExtractPath, Parser.Parser.ImageFileExtensions);
|
||||
// Filter out images that aren't in bookmarks
|
||||
Array.Sort(files, _numericComparer);
|
||||
totalFilePaths.AddRange(files.Where((t, i) => chapterPages.Contains(i)));
|
||||
totalFilePaths.AddRange(files.Where((_, i) => chapterPages.Contains(i)));
|
||||
}
|
||||
|
||||
|
||||
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(totalFilePaths,
|
||||
var (fileBytes, _) = await _archiveService.CreateZipForDownload(totalFilePaths,
|
||||
tempFolder);
|
||||
DirectoryService.ClearAndDeleteDirectory(fullExtractPath);
|
||||
return File(fileBytes, DefaultContentType, $"{series.Name} - Bookmarks.zip");
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ namespace API.Controllers
|
|||
/// <summary>
|
||||
/// Removes all bookmarks for all chapters linked to a Series
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("remove-bookmarks")]
|
||||
public async Task<ActionResult> RemoveBookmarks(RemoveBookmarkForSeriesDto dto)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ using API.Interfaces;
|
|||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
|||
using API.DTOs.Stats;
|
||||
using API.DTOs.Update;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services.Tasks;
|
||||
using Kavita.Common;
|
||||
|
|
@ -26,11 +25,10 @@ namespace API.Controllers
|
|||
private readonly IBackupService _backupService;
|
||||
private readonly IArchiveService _archiveService;
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly ITaskScheduler _taskScheduler;
|
||||
private readonly IVersionUpdaterService _versionUpdaterService;
|
||||
|
||||
public ServerController(IHostApplicationLifetime applicationLifetime, ILogger<ServerController> logger, IConfiguration config,
|
||||
IBackupService backupService, IArchiveService archiveService, ICacheService cacheService, ITaskScheduler taskScheduler,
|
||||
IBackupService backupService, IArchiveService archiveService, ICacheService cacheService,
|
||||
IVersionUpdaterService versionUpdaterService)
|
||||
{
|
||||
_applicationLifetime = applicationLifetime;
|
||||
|
|
@ -39,7 +37,6 @@ namespace API.Controllers
|
|||
_backupService = backupService;
|
||||
_archiveService = archiveService;
|
||||
_cacheService = cacheService;
|
||||
_taskScheduler = taskScheduler;
|
||||
_versionUpdaterService = versionUpdaterService;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class SeriesByIdsDto
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using API.Entities;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
|
|
|
|||
|
|
@ -60,11 +60,11 @@ namespace API.Data
|
|||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
|
||||
// Port and LoggingLevel are managed in appSettings.json. Update the DB values to match
|
||||
context.ServerSetting.FirstOrDefault(s => s.Key == ServerSettingKey.Port).Value =
|
||||
context.ServerSetting.First(s => s.Key == ServerSettingKey.Port).Value =
|
||||
Configuration.Port + string.Empty;
|
||||
context.ServerSetting.FirstOrDefault(s => s.Key == ServerSettingKey.LoggingLevel).Value =
|
||||
context.ServerSetting.First(s => s.Key == ServerSettingKey.LoggingLevel).Value =
|
||||
Configuration.LogLevel + string.Empty;
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
|
|
@ -74,11 +74,11 @@ namespace API.Data
|
|||
public static async Task SeedSeriesMetadata(DataContext context)
|
||||
{
|
||||
await context.Database.EnsureCreatedAsync();
|
||||
|
||||
|
||||
context.Database.EnsureCreated();
|
||||
var series = await context.Series
|
||||
.Include(s => s.Metadata).ToListAsync();
|
||||
|
||||
|
||||
foreach (var s in series)
|
||||
{
|
||||
s.Metadata ??= new SeriesMetadata();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Comparators;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Filtering;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Interfaces;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Reader;
|
||||
using API.Entities;
|
||||
using API.Interfaces;
|
||||
using AutoMapper;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using API.Entities.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Entities
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using API.Entities.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Entities
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using API.Comparators;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,15 @@ namespace API.Extensions
|
|||
{
|
||||
public static class FileInfoExtensions
|
||||
{
|
||||
public static bool DoesLastWriteMatch(this FileInfo fileInfo, DateTime comparison)
|
||||
/// <summary>
|
||||
/// Checks if the last write time of the file is after the passed date
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <param name="comparison"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasFileBeenModifiedSince(this FileInfo fileInfo, DateTime comparison)
|
||||
{
|
||||
return comparison.Equals(fileInfo.LastWriteTime);
|
||||
}
|
||||
|
||||
public static bool IsLastWriteLessThan(this FileInfo fileInfo, DateTime comparison)
|
||||
{
|
||||
return fileInfo.LastWriteTime < comparison;
|
||||
return fileInfo?.LastWriteTime > comparison;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs.Update;
|
||||
using API.Services.Tasks;
|
||||
|
||||
namespace API.Interfaces.Services
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ namespace API.Services
|
|||
private readonly IDirectoryService _directoryService;
|
||||
private static readonly RecyclableMemoryStreamManager StreamManager = new();
|
||||
private readonly NaturalSortComparer _comparer;
|
||||
private const string ComicInfoFilename = "comicinfo";
|
||||
|
||||
public ArchiveService(ILogger<ArchiveService> logger, IDirectoryService directoryService)
|
||||
{
|
||||
|
|
@ -297,7 +298,7 @@ namespace API.Services
|
|||
foreach (var entry in entries)
|
||||
{
|
||||
var filename = Path.GetFileNameWithoutExtension(entry.Key).ToLower();
|
||||
if (filename.EndsWith("comicinfo")
|
||||
if (filename.EndsWith(ComicInfoFilename)
|
||||
&& !filename.StartsWith(Parser.Parser.MacOsMetadataFileStartsWith)
|
||||
&& !Parser.Parser.HasBlacklistedFolderInPath(entry.Key)
|
||||
&& Parser.Parser.IsXml(entry.Key))
|
||||
|
|
@ -334,7 +335,7 @@ namespace API.Services
|
|||
_logger.LogDebug("Using default compression handling");
|
||||
using var archive = ZipFile.OpenRead(archivePath);
|
||||
var entry = archive.Entries.SingleOrDefault(x => !Parser.Parser.HasBlacklistedFolderInPath(x.FullName)
|
||||
&& Path.GetFileNameWithoutExtension(x.Name).ToLower() == "comicinfo"
|
||||
&& Path.GetFileNameWithoutExtension(x.Name)?.ToLower() == ComicInfoFilename
|
||||
&& !Path.GetFileNameWithoutExtension(x.Name).StartsWith(Parser.Parser.MacOsMetadataFileStartsWith)
|
||||
&& Parser.Parser.IsXml(x.FullName));
|
||||
if (entry != null)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using API.Entities.Enums;
|
|||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using Kavita.Common;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ namespace API.Services.Clients
|
|||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly ILogger<StatsApiClient> _logger;
|
||||
#pragma warning disable S1075
|
||||
private const string ApiUrl = "http://stats.kavitareader.com";
|
||||
#pragma warning restore S1075
|
||||
|
||||
public StatsApiClient(HttpClient client, ILogger<StatsApiClient> logger)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ namespace API.Services
|
|||
var fileCount = 0;
|
||||
|
||||
// Determine whether to parallelize file processing on each folder based on processor count.
|
||||
var procCount = Environment.ProcessorCount;
|
||||
//var procCount = Environment.ProcessorCount;
|
||||
|
||||
// Data structure to hold names of subfolders to be examined for files.
|
||||
var dirs = new Stack<string>();
|
||||
|
|
|
|||
|
|
@ -67,7 +67,10 @@ namespace API.Services
|
|||
public void UpdateMetadata(Chapter chapter, bool forceUpdate)
|
||||
{
|
||||
var firstFile = chapter.Files.OrderBy(x => x.Chapter).FirstOrDefault();
|
||||
if (!chapter.CoverImageLocked && ShouldFindCoverImage(chapter.CoverImage, forceUpdate) && firstFile != null && !new FileInfo(firstFile.FilePath).IsLastWriteLessThan(firstFile.LastModified))
|
||||
if (!chapter.CoverImageLocked
|
||||
&& ShouldFindCoverImage(chapter.CoverImage, forceUpdate)
|
||||
&& firstFile != null
|
||||
&& (forceUpdate || new FileInfo(firstFile.FilePath).HasFileBeenModifiedSince(firstFile.LastModified)))
|
||||
{
|
||||
chapter.Files ??= new List<MangaFile>();
|
||||
chapter.CoverImage = GetCoverImage(firstFile);
|
||||
|
|
@ -88,19 +91,7 @@ namespace API.Services
|
|||
|
||||
if (firstChapter == null) return;
|
||||
|
||||
// Skip calculating Cover Image (I/O) if the chapter already has it set
|
||||
if (!firstChapter.CoverImageLocked && ShouldFindCoverImage(firstChapter.CoverImage, forceUpdate))
|
||||
{
|
||||
// NOTE: Why do I do this? By the time this method gets executed, the chapter has already been calculated for
|
||||
// Plus how can we have a volume without at least 1 chapter?
|
||||
var firstFile = firstChapter.Files.OrderBy(x => x.Chapter).FirstOrDefault();
|
||||
if (firstFile != null && !new FileInfo(firstFile.FilePath).IsLastWriteLessThan(firstFile.LastModified))
|
||||
{
|
||||
firstChapter.CoverImage = GetCoverImage(firstFile);
|
||||
}
|
||||
}
|
||||
volume.CoverImage = firstChapter.CoverImage;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -119,7 +119,6 @@ namespace API.Services
|
|||
BackgroundJob.Enqueue(() => _cleanupService.Cleanup());
|
||||
}
|
||||
|
||||
|
||||
public void CleanupChapters(int[] chapterIds)
|
||||
{
|
||||
BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds));
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ namespace API.Services.Tasks
|
|||
totalFiles, parsedSeries.Keys.Count, sw.ElapsedMilliseconds + scanElapsedTime, series.Name);
|
||||
|
||||
CleanupDbEntities();
|
||||
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadata(libraryId, forceUpdate));
|
||||
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadataForSeries(libraryId, seriesId));
|
||||
BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds));
|
||||
}
|
||||
else
|
||||
|
|
@ -132,11 +132,14 @@ namespace API.Services.Tasks
|
|||
{
|
||||
ScanLibrary(lib.Id, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scans a library for file changes. If force update passed, all entities will be rechecked for new cover images and comicInfo.xml changes.
|
||||
/// Scans a library for file changes.
|
||||
/// Will kick off a scheduled background task to refresh metadata,
|
||||
/// ie) all entities will be rechecked for new cover images and comicInfo.xml changes
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="forceUpdate"></param>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ namespace API.Services.Tasks
|
|||
{
|
||||
public override HttpMessageHandler CreateMessageHandler() {
|
||||
return new HttpClientHandler {
|
||||
ServerCertificateCustomValidationCallback = (a, b, c, d) => true
|
||||
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ namespace API.Services.Tasks
|
|||
return updates.Select(CreateDto);
|
||||
}
|
||||
|
||||
private UpdateNotificationDto? CreateDto(GithubReleaseMetadata update)
|
||||
private UpdateNotificationDto CreateDto(GithubReleaseMetadata update)
|
||||
{
|
||||
if (update == null || string.IsNullOrEmpty(update.Tag_Name)) return null;
|
||||
var version = update.Tag_Name.Replace("v", string.Empty);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ namespace API
|
|||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Kavita API", Version = "v1" });
|
||||
var filePath = Path.Combine(System.AppContext.BaseDirectory, "API.xml");
|
||||
var filePath = Path.Combine(AppContext.BaseDirectory, "API.xml");
|
||||
c.IncludeXmlComments(filePath);
|
||||
});
|
||||
services.AddResponseCompression(options =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue