.NET 6 Coding Patterns + Unit Tests (#823)
* Refactored all files to have Interfaces within the same file. Started moving over to file-scoped namespaces. * Refactored common methods for getting underlying file's cover, pages, and extracting into 1 interface. * More refactoring around removing dependence on explicit filetype testing for getting information. * Code is buildable, tests are broken. Huge refactor (not completed) which makes most of DirectoryService testable with a mock filesystem (and thus the services that utilize it). * Finished porting DirectoryService to use mocked filesystem implementation. * Added a null check * Added a null check * Finished all unit tests for DirectoryService. * Some misc cleanup on the code * Fixed up some bugs from refactoring scan loop. * Implemented CleanupService testing and refactored more of DirectoryService to be non-static. Fixed a bug where cover file cleanup wasn't properly finding files due to a regex bug. * Fixed an issue in CleanupBackup() where we weren't properly selecting database files older than 30 days. Finished CleanupService Tests. * Refactored Flatten and RemoveNonImages to directory service to allow CacheService to be testable. * Finally have CacheService tested. Rewrote GetCachedPagePath() to be much more straightforward & performant. * Updated DefaultParserTests.cs to contain all existing tests and follow new test layout format. * All tests fixed up
This commit is contained in:
parent
bf1876ff44
commit
bbe8f800f6
115 changed files with 6734 additions and 5370 deletions
|
|
@ -4,12 +4,11 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.Data;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Account;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using AutoMapper;
|
||||
using Kavita.Common;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Reader;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using HtmlAgilityPack;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs.CollectionTags;
|
||||
using API.Entities;
|
||||
using API.Entities.Metadata;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Comparators;
|
||||
using API.Data;
|
||||
using API.DTOs.Downloads;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using API.SignalR;
|
||||
using Kavita.Common;
|
||||
|
|
@ -47,21 +46,21 @@ namespace API.Controllers
|
|||
public async Task<ActionResult<long>> GetVolumeSize(int volumeId)
|
||||
{
|
||||
var files = await _unitOfWork.VolumeRepository.GetFilesForVolume(volumeId);
|
||||
return Ok(DirectoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
return Ok(_directoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
}
|
||||
|
||||
[HttpGet("chapter-size")]
|
||||
public async Task<ActionResult<long>> GetChapterSize(int chapterId)
|
||||
{
|
||||
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId);
|
||||
return Ok(DirectoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
return Ok(_directoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
}
|
||||
|
||||
[HttpGet("series-size")]
|
||||
public async Task<ActionResult<long>> GetSeriesSize(int seriesId)
|
||||
{
|
||||
var files = await _unitOfWork.SeriesRepository.GetFilesForSeries(seriesId);
|
||||
return Ok(DirectoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
return Ok(_directoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
}
|
||||
|
||||
[HttpGet("volume")]
|
||||
|
|
@ -141,13 +140,13 @@ namespace API.Controllers
|
|||
var totalFilePaths = new List<string>();
|
||||
|
||||
var tempFolder = $"download_{series.Id}_bookmarks";
|
||||
var fullExtractPath = Path.Join(DirectoryService.TempDirectory, tempFolder);
|
||||
if (new DirectoryInfo(fullExtractPath).Exists)
|
||||
var fullExtractPath = Path.Join(_directoryService.TempDirectory, tempFolder);
|
||||
if (_directoryService.FileSystem.DirectoryInfo.FromDirectoryName(fullExtractPath).Exists)
|
||||
{
|
||||
return BadRequest(
|
||||
"Server is currently processing this exact download. Please try again in a few minutes.");
|
||||
}
|
||||
DirectoryService.ExistOrCreate(fullExtractPath);
|
||||
_directoryService.ExistOrCreate(fullExtractPath);
|
||||
|
||||
var uniqueChapterIds = downloadBookmarkDto.Bookmarks.Select(b => b.ChapterId).Distinct().ToList();
|
||||
|
||||
|
|
@ -160,16 +159,16 @@ namespace API.Controllers
|
|||
switch (series.Format)
|
||||
{
|
||||
case MangaFormat.Image:
|
||||
DirectoryService.ExistOrCreate(chapterExtractPath);
|
||||
_directoryService.ExistOrCreate(chapterExtractPath);
|
||||
_directoryService.CopyFilesToDirectory(mangaFiles.Select(f => f.FilePath), chapterExtractPath, $"{chapterId}_");
|
||||
break;
|
||||
case MangaFormat.Archive:
|
||||
case MangaFormat.Pdf:
|
||||
_cacheService.ExtractChapterFiles(chapterExtractPath, mangaFiles.ToList());
|
||||
var originalFiles = DirectoryService.GetFilesWithExtension(chapterExtractPath,
|
||||
var originalFiles = _directoryService.GetFilesWithExtension(chapterExtractPath,
|
||||
Parser.Parser.ImageFileExtensions);
|
||||
_directoryService.CopyFilesToDirectory(originalFiles, chapterExtractPath, $"{chapterId}_");
|
||||
DirectoryService.DeleteFiles(originalFiles);
|
||||
_directoryService.DeleteFiles(originalFiles);
|
||||
break;
|
||||
case MangaFormat.Epub:
|
||||
return BadRequest("Series is not in a valid format.");
|
||||
|
|
@ -177,7 +176,7 @@ namespace API.Controllers
|
|||
return BadRequest("Series is not in a valid format. Please rescan series and try again.");
|
||||
}
|
||||
|
||||
var files = DirectoryService.GetFilesWithExtension(chapterExtractPath, Parser.Parser.ImageFileExtensions);
|
||||
var files = _directoryService.GetFilesWithExtension(chapterExtractPath, Parser.Parser.ImageFileExtensions);
|
||||
// Filter out images that aren't in bookmarks
|
||||
Array.Sort(files, _numericComparer);
|
||||
totalFilePaths.AddRange(files.Where((_, i) => chapterPages.Contains(i)));
|
||||
|
|
@ -186,7 +185,7 @@ namespace API.Controllers
|
|||
|
||||
var (fileBytes, _) = await _archiveService.CreateZipForDownload(totalFilePaths,
|
||||
tempFolder);
|
||||
DirectoryService.ClearAndDeleteDirectory(fullExtractPath);
|
||||
_directoryService.ClearAndDeleteDirectory(fullExtractPath);
|
||||
return File(fileBytes, DefaultContentType, $"{series.Name} - Bookmarks.zip");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using System.IO;
|
||||
using API.Interfaces;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers
|
||||
|
|
@ -12,7 +12,7 @@ namespace API.Controllers
|
|||
|
||||
public FallbackController(ITaskScheduler taskScheduler)
|
||||
{
|
||||
// This is used to load TaskScheduler on startup without having to navigate to a Controller that uses.
|
||||
// This is used to load TaskScheduler on startup without having to navigate to a Controller that uses.
|
||||
_taskScheduler = taskScheduler;
|
||||
}
|
||||
|
||||
|
|
@ -21,4 +21,4 @@ namespace API.Controllers
|
|||
return PhysicalFile(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "index.html"), "text/HTML");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
|
@ -13,11 +13,13 @@ namespace API.Controllers
|
|||
public class ImageController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImageController(IUnitOfWork unitOfWork)
|
||||
public ImageController(IUnitOfWork unitOfWork, IDirectoryService directoryService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_directoryService = directoryService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -28,12 +30,12 @@ namespace API.Controllers
|
|||
[HttpGet("chapter-cover")]
|
||||
public async Task<ActionResult> GetChapterCoverImage(int chapterId)
|
||||
{
|
||||
var path = Path.Join(DirectoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId));
|
||||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No cover image");
|
||||
var format = Path.GetExtension(path).Replace(".", "");
|
||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId));
|
||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
|
||||
|
||||
Response.AddCacheHeader(path);
|
||||
return PhysicalFile(path, "image/" + format, Path.GetFileName(path));
|
||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -44,12 +46,12 @@ namespace API.Controllers
|
|||
[HttpGet("volume-cover")]
|
||||
public async Task<ActionResult> GetVolumeCoverImage(int volumeId)
|
||||
{
|
||||
var path = Path.Join(DirectoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId));
|
||||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No cover image");
|
||||
var format = Path.GetExtension(path).Replace(".", "");
|
||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId));
|
||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
|
||||
|
||||
Response.AddCacheHeader(path);
|
||||
return PhysicalFile(path, "image/" + format, Path.GetFileName(path));
|
||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -60,12 +62,12 @@ namespace API.Controllers
|
|||
[HttpGet("series-cover")]
|
||||
public async Task<ActionResult> GetSeriesCoverImage(int seriesId)
|
||||
{
|
||||
var path = Path.Join(DirectoryService.CoverImageDirectory, await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId));
|
||||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No cover image");
|
||||
var format = Path.GetExtension(path).Replace(".", "");
|
||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId));
|
||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
|
||||
|
||||
Response.AddCacheHeader(path);
|
||||
return PhysicalFile(path, "image/" + format, Path.GetFileName(path));
|
||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -76,12 +78,12 @@ namespace API.Controllers
|
|||
[HttpGet("collection-cover")]
|
||||
public async Task<ActionResult> GetCollectionCoverImage(int collectionTagId)
|
||||
{
|
||||
var path = Path.Join(DirectoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId));
|
||||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No cover image");
|
||||
var format = Path.GetExtension(path).Replace(".", "");
|
||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId));
|
||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
|
||||
|
||||
Response.AddCacheHeader(path);
|
||||
return PhysicalFile(path, "image/" + format, Path.GetFileName(path));
|
||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Reader;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ namespace API.Controllers
|
|||
|
||||
try
|
||||
{
|
||||
var (path, _) = await _cacheService.GetCachedPagePath(chapter, page);
|
||||
var path = _cacheService.GetCachedPagePath(chapter, page);
|
||||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {page}");
|
||||
var format = Path.GetExtension(path).Replace(".", "");
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ namespace API.Controllers
|
|||
LibraryId = dto.LibraryId,
|
||||
IsSpecial = dto.IsSpecial,
|
||||
Pages = dto.Pages,
|
||||
ChapterTitle = dto.ChapterTitle
|
||||
ChapterTitle = dto.ChapterTitle ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Comparators;
|
||||
using API.Data;
|
||||
using API.DTOs.ReadingLists;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Interfaces;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ using API.Data;
|
|||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Filtering;
|
||||
using API.DTOs.Metadata;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Interfaces;
|
||||
using API.Services;
|
||||
using API.SignalR;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ using System.Threading.Tasks;
|
|||
using API.DTOs.Stats;
|
||||
using API.DTOs.Update;
|
||||
using API.Extensions;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using API.Services.Tasks;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
@ -26,10 +27,11 @@ namespace API.Controllers
|
|||
private readonly ICacheService _cacheService;
|
||||
private readonly IVersionUpdaterService _versionUpdaterService;
|
||||
private readonly IStatsService _statsService;
|
||||
private readonly ICleanupService _cleanupService;
|
||||
|
||||
public ServerController(IHostApplicationLifetime applicationLifetime, ILogger<ServerController> logger, IConfiguration config,
|
||||
IBackupService backupService, IArchiveService archiveService, ICacheService cacheService,
|
||||
IVersionUpdaterService versionUpdaterService, IStatsService statsService)
|
||||
IVersionUpdaterService versionUpdaterService, IStatsService statsService, ICleanupService cleanupService)
|
||||
{
|
||||
_applicationLifetime = applicationLifetime;
|
||||
_logger = logger;
|
||||
|
|
@ -39,6 +41,7 @@ namespace API.Controllers
|
|||
_cacheService = cacheService;
|
||||
_versionUpdaterService = versionUpdaterService;
|
||||
_statsService = statsService;
|
||||
_cleanupService = cleanupService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -62,7 +65,7 @@ namespace API.Controllers
|
|||
public ActionResult ClearCache()
|
||||
{
|
||||
_logger.LogInformation("{UserName} is clearing cache of server from admin dashboard", User.GetUsername());
|
||||
_cacheService.Cleanup();
|
||||
_cleanupService.CleanupCacheDirectory();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
|
@ -93,7 +96,7 @@ namespace API.Controllers
|
|||
[HttpGet("logs")]
|
||||
public async Task<ActionResult> GetLogs()
|
||||
{
|
||||
var files = _backupService.LogFiles(_config.GetMaxRollingFiles(), _config.GetLoggingFileName());
|
||||
var files = _backupService.GetLogFiles(_config.GetMaxRollingFiles(), _config.GetLoggingFileName());
|
||||
try
|
||||
{
|
||||
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files, "logs");
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs.Settings;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Helpers.Converters;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using Kavita.Common;
|
||||
using Kavita.Common.Extensions;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs.Uploads;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue