diff --git a/API.Tests/Services/ArchiveServiceTests.cs b/API.Tests/Services/ArchiveServiceTests.cs index a05beaee6..84163d55a 100644 --- a/API.Tests/Services/ArchiveServiceTests.cs +++ b/API.Tests/Services/ArchiveServiceTests.cs @@ -67,15 +67,8 @@ namespace API.Tests.Services { var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/Archives"); var sw = Stopwatch.StartNew(); - try - { - Assert.Equal(expected, _archiveService.GetNumberOfPagesFromArchive(Path.Join(testDirectory, archivePath))); - _testOutputHelper.WriteLine($"Processed Original in {sw.ElapsedMilliseconds} ms"); - } - catch (Exception e) - { - _testOutputHelper.WriteLine("Could not process"); - } + Assert.Equal(expected, _archiveService.GetNumberOfPagesFromArchive(Path.Join(testDirectory, archivePath))); + _testOutputHelper.WriteLine($"Processed Original in {sw.ElapsedMilliseconds} ms"); } @@ -95,15 +88,8 @@ namespace API.Tests.Services var sw = Stopwatch.StartNew(); var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/Archives"); - try - { - Assert.Equal(expected, _archiveService.IsValidArchive(Path.Join(testDirectory, archivePath))); - _testOutputHelper.WriteLine($"Processed Original in {sw.ElapsedMilliseconds} ms"); - } - catch (Exception e) - { - _testOutputHelper.WriteLine("Could not process"); - } + Assert.Equal(expected, _archiveService.IsValidArchive(Path.Join(testDirectory, archivePath))); + _testOutputHelper.WriteLine($"Processed Original in {sw.ElapsedMilliseconds} ms"); } diff --git a/API.Tests/Services/CacheServiceTests.cs b/API.Tests/Services/CacheServiceTests.cs index bdb2f4b62..2072dae1f 100644 --- a/API.Tests/Services/CacheServiceTests.cs +++ b/API.Tests/Services/CacheServiceTests.cs @@ -1,15 +1,4 @@ -using System.Collections.Generic; -using System.IO; -using API.Data; -using API.Entities; -using API.Interfaces; -using API.Interfaces.Services; -using API.Services; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Xunit; - -namespace API.Tests.Services +namespace API.Tests.Services { public class CacheServiceTests { @@ -70,7 +59,7 @@ namespace API.Tests.Services // // Chapter = 0, // // FilePath = archivePath, // // Format = MangaFormat.Archive, - // // NumberOfPages = 1, + // // Pages = 1, // // } // // }, // // Name = "1", diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs index 924242c9e..04d0bf9d5 100644 --- a/API/Controllers/AccountController.cs +++ b/API/Controllers/AccountController.cs @@ -10,7 +10,6 @@ using API.Extensions; using API.Interfaces; using API.Interfaces.Services; using AutoMapper; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/API/Controllers/FallbackController.cs b/API/Controllers/FallbackController.cs index 82ed0e3f6..56962a3d6 100644 --- a/API/Controllers/FallbackController.cs +++ b/API/Controllers/FallbackController.cs @@ -6,6 +6,7 @@ namespace API.Controllers { public class FallbackController : Controller { + // ReSharper disable once S4487 private readonly ITaskScheduler _taskScheduler; public FallbackController(ITaskScheduler taskScheduler) diff --git a/API/Controllers/ImageController.cs b/API/Controllers/ImageController.cs index c117abc9e..62fbd51ae 100644 --- a/API/Controllers/ImageController.cs +++ b/API/Controllers/ImageController.cs @@ -1,28 +1,16 @@ -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using API.DTOs; +using System.Threading.Tasks; using API.Extensions; using API.Interfaces; -using API.Interfaces.Services; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace API.Controllers { public class ImageController : BaseApiController { - private readonly IDirectoryService _directoryService; - private readonly ICacheService _cacheService; - private readonly ILogger _logger; private readonly IUnitOfWork _unitOfWork; - public ImageController(IDirectoryService directoryService, ICacheService cacheService, - ILogger logger, IUnitOfWork unitOfWork) + public ImageController(IUnitOfWork unitOfWork) { - _directoryService = directoryService; - _cacheService = cacheService; - _logger = logger; _unitOfWork = unitOfWork; } diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs index 4b8c776df..bc085114b 100644 --- a/API/Controllers/LibraryController.cs +++ b/API/Controllers/LibraryController.cs @@ -147,7 +147,6 @@ namespace API.Controllers [HttpPost("scan")] public ActionResult Scan(int libraryId) { - // TODO: We shouldn't queue up a job if one is already in progress _taskScheduler.ScanLibrary(libraryId); return Ok(); } diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs index 21dce8411..d157980ad 100644 --- a/API/Controllers/ReaderController.cs +++ b/API/Controllers/ReaderController.cs @@ -35,7 +35,7 @@ namespace API.Controllers var chapter = await _cacheService.Ensure(chapterId); if (chapter == null) return BadRequest("There was an issue finding image file for reading"); - var (path, mangaFile) = await _cacheService.GetCachedPagePath(chapter, page); + var (path, _) = await _cacheService.GetCachedPagePath(chapter, page); if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {page}"); var content = await _directoryService.ReadFileAsync(path); @@ -53,7 +53,7 @@ namespace API.Controllers var chapter = await _cacheService.Ensure(chapterId); if (chapter == null) return BadRequest("There was an issue finding image file for reading"); - var (path, mangaFile) = await _cacheService.GetCachedPagePath(chapter, 0); + var (_, mangaFile) = await _cacheService.GetCachedPagePath(chapter, 0); return Ok(mangaFile.FilePath); } diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs index c1e4c9b73..e63be3eb5 100644 --- a/API/Controllers/SeriesController.cs +++ b/API/Controllers/SeriesController.cs @@ -105,9 +105,9 @@ namespace API.Controllers if (series == null) return BadRequest("Series does not exist"); - // TODO: check if new name isn't an existing series - var existingSeries = await _unitOfWork.SeriesRepository.GetSeriesByNameAsync(updateSeries.Name); // NOTE: This isnt checking library - if (existingSeries != null && existingSeries.Id != series.Id) + // TODO: Ensure we check against Library for Series Name change + var existingSeries = await _unitOfWork.SeriesRepository.GetSeriesByNameAsync(updateSeries.Name); + if (existingSeries != null && existingSeries.Id != series.Id ) { return BadRequest("A series already exists with this name. Name must be unique."); } @@ -115,8 +115,7 @@ namespace API.Controllers series.LocalizedName = updateSeries.LocalizedName; series.SortName = updateSeries.SortName; series.Summary = updateSeries.Summary; - //series.CoverImage = updateSeries.CoverImage; - + _unitOfWork.SeriesRepository.Update(series); if (await _unitOfWork.Complete()) @@ -139,16 +138,5 @@ namespace API.Controllers var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); return Ok(await _unitOfWork.SeriesRepository.GetInProgress(user.Id, libraryId, limit)); } - - [HttpGet("continue-reading")] - public async Task>> GetContinueReading(int libraryId = 0, int limit = 20) - { - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); - return Ok(await _unitOfWork.VolumeRepository.GetContinueReading(user.Id, libraryId, limit)); - } - - - - } } \ No newline at end of file diff --git a/API/Controllers/ServerController.cs b/API/Controllers/ServerController.cs index 2da9d0b2f..348929523 100644 --- a/API/Controllers/ServerController.cs +++ b/API/Controllers/ServerController.cs @@ -21,17 +21,15 @@ namespace API.Controllers private readonly IConfiguration _config; private readonly IDirectoryService _directoryService; private readonly IBackupService _backupService; - private readonly ITaskScheduler _taskScheduler; public ServerController(IHostApplicationLifetime applicationLifetime, ILogger logger, IConfiguration config, - IDirectoryService directoryService, IBackupService backupService, ITaskScheduler taskScheduler) + IDirectoryService directoryService, IBackupService backupService) { _applicationLifetime = applicationLifetime; _logger = logger; _config = config; _directoryService = directoryService; _backupService = backupService; - _taskScheduler = taskScheduler; } [HttpPost("restart")] diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index 2e0bbbfeb..d149aa0d4 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -10,7 +10,6 @@ using API.Helpers.Converters; using API.Interfaces; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -110,7 +109,7 @@ namespace API.Controllers [HttpGet("log-levels")] public ActionResult> GetLogLevels() { - return Ok(new string[] {"Trace", "Debug", "Information", "Warning", "Critical", "None"}); + return Ok(new [] {"Trace", "Debug", "Information", "Warning", "Critical", "None"}); } } } \ No newline at end of file diff --git a/API/DTOs/BookmarkDto.cs b/API/DTOs/BookmarkDto.cs index de7f1b6a7..e2a1c6c2d 100644 --- a/API/DTOs/BookmarkDto.cs +++ b/API/DTOs/BookmarkDto.cs @@ -1,4 +1,4 @@ -namespace API.Data +namespace API.DTOs { public class BookmarkDto { diff --git a/API/Data/LibraryRepository.cs b/API/Data/LibraryRepository.cs index c33c42281..9a46b44a4 100644 --- a/API/Data/LibraryRepository.cs +++ b/API/Data/LibraryRepository.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; diff --git a/API/Data/Migrations/20210322212724_MangaFileToPages.Designer.cs b/API/Data/Migrations/20210322212724_MangaFileToPages.Designer.cs new file mode 100644 index 000000000..f5d2d7ef9 --- /dev/null +++ b/API/Data/Migrations/20210322212724_MangaFileToPages.Designer.cs @@ -0,0 +1,733 @@ +// +using System; +using API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20210322212724_MangaFileToPages")] + partial class MangaFileToPages + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.1"); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("HideReadOnDetails") + .HasColumnType("INTEGER"); + + b.Property("PageSplitOption") + .HasColumnType("INTEGER"); + + b.Property("ReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("ScalingOption") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId") + .IsUnique(); + + b.ToTable("AppUserPreferences"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("PagesRead") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserProgresses"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("INTEGER"); + + b.Property("Review") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserRating"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("BLOB"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("Range") + .HasColumnType("TEXT"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("VolumeId"); + + b.ToTable("Chapter"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("FolderPath"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("MangaFile"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("BLOB"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("LocalizedName") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("OriginalName") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId") + .IsUnique(); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("API.Entities.ServerSetting", b => + { + b.Property("Key") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("ServerSetting"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("BLOB"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("IsSpecial") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("Volume"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.Property("AppUsersId") + .HasColumnType("INTEGER"); + + b.Property("LibrariesId") + .HasColumnType("INTEGER"); + + b.HasKey("AppUsersId", "LibrariesId"); + + b.HasIndex("LibrariesId"); + + b.ToTable("AppUserLibrary"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithOne("UserPreferences") + .HasForeignKey("API.Entities.AppUserPreferences", "AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Progresses") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Ratings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.HasOne("API.Entities.AppRole", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUser", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.HasOne("API.Entities.Volume", "Volume") + .WithMany("Chapters") + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Folders") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("Files") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Series") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Volumes") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("AppUsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", null) + .WithMany() + .HasForeignKey("LibrariesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("API.Entities.AppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Navigation("Progresses"); + + b.Navigation("Ratings"); + + b.Navigation("UserPreferences"); + + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Navigation("Files"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Navigation("Folders"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Navigation("Volumes"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Navigation("Chapters"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/Migrations/20210322212724_MangaFileToPages.cs b/API/Data/Migrations/20210322212724_MangaFileToPages.cs new file mode 100644 index 000000000..63fecfb72 --- /dev/null +++ b/API/Data/Migrations/20210322212724_MangaFileToPages.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace API.Data.Migrations +{ + public partial class MangaFileToPages : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "NumberOfPages", + table: "MangaFile", + newName: "Pages"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Pages", + table: "MangaFile", + newName: "NumberOfPages"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index c0fcd6f87..11dff82eb 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -318,7 +318,7 @@ namespace API.Data.Migrations b.Property("Format") .HasColumnType("INTEGER"); - b.Property("NumberOfPages") + b.Property("Pages") .HasColumnType("INTEGER"); b.HasKey("Id"); diff --git a/API/Data/SeriesRepository.cs b/API/Data/SeriesRepository.cs index 52be7dac7..73b49ef1e 100644 --- a/API/Data/SeriesRepository.cs +++ b/API/Data/SeriesRepository.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -12,7 +10,6 @@ using API.Interfaces; using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.Logging; namespace API.Data @@ -306,7 +303,6 @@ namespace API.Data /// public async Task> GetInProgress(int userId, int libraryId, int limit) { - // TODO: Idea: Put Total PagesRead and as return so that we can show a progress bar for full series read progress var series = await _context.Series .Join(_context.AppUserProgresses, s => s.Id, progress => progress.SeriesId, (s, progress) => new { diff --git a/API/Data/UnitOfWork.cs b/API/Data/UnitOfWork.cs index aa3c9ab5f..ae2f909a9 100644 --- a/API/Data/UnitOfWork.cs +++ b/API/Data/UnitOfWork.cs @@ -26,7 +26,7 @@ namespace API.Data public IUserRepository UserRepository => new UserRepository(_context, _userManager); public ILibraryRepository LibraryRepository => new LibraryRepository(_context, _mapper); - public IVolumeRepository VolumeRepository => new VolumeRepository(_context, _mapper, _logger); + public IVolumeRepository VolumeRepository => new VolumeRepository(_context, _mapper); public ISettingsRepository SettingsRepository => new SettingsRepository(_context, _mapper); diff --git a/API/Data/VolumeRepository.cs b/API/Data/VolumeRepository.cs index f0e183805..6b9e541ea 100644 --- a/API/Data/VolumeRepository.cs +++ b/API/Data/VolumeRepository.cs @@ -1,15 +1,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using API.Comparators; using API.DTOs; using API.Entities; -using API.Extensions; using API.Interfaces; using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; namespace API.Data { @@ -17,13 +14,11 @@ namespace API.Data { private readonly DataContext _context; private readonly IMapper _mapper; - private readonly ILogger _logger; - public VolumeRepository(DataContext context, IMapper mapper, ILogger logger) + public VolumeRepository(DataContext context, IMapper mapper) { _context = context; _mapper = mapper; - _logger = logger; } public void Update(Volume volume) @@ -89,123 +84,5 @@ namespace API.Data .AsNoTracking() .ToListAsync(); } - - /// - /// Gets the first (ordered) volume/chapter in a series where the user has progress on it. Only completed volumes/chapters, next entity shouldn't - /// have any read progress on it. - /// - /// - /// - /// - /// - public async Task> GetContinueReading(int userId, int libraryId, int limit) - { - /** TODO: Fix this SQL - * SELECT * FROM - ( - SELECT * FROM Chapter C WHERE C.VolumeId IN (SELECT Id from Volume where SeriesId = 1912) - ) C INNER JOIN AppUserProgresses AUP ON AUP.ChapterId = C.Id - INNER JOIN Series S ON AUP.SeriesId = S.Id - WHERE AUP.AppUserId = 1 AND AUP.PagesRead < C.Pages - */ - _logger.LogInformation("Get Continue Reading"); - var volumeQuery = _context.Volume - .Join(_context.AppUserProgresses, v => v.Id, aup => aup.VolumeId, (volume, progress) => new - { - volume, - progress - }) - .Where(arg => arg.volume.SeriesId == arg.progress.SeriesId && arg.progress.AppUserId == userId) - .AsNoTracking() - .Select(arg => new - { - VolumeId = arg.volume.Id, - VolumeNumber = arg.volume.Number - }); // I think doing a join on this would be better - - var volumeIds = (await volumeQuery.ToListAsync()).Select(s => s.VolumeId); - - var chapters2 = await _context.Chapter.Where(c => volumeIds.Contains(c.VolumeId)) - .Join(_context.AppUserProgresses, chapter => chapter.Id, aup => aup.ChapterId, (chapter, progress) => - new - { - chapter, - progress - }) - .Join(_context.Series, arg => arg.progress.SeriesId, s => s.Id, (arg, series) => new - { - Chapter = arg.chapter, - Progress = arg.progress, - Series = series - }) - .Where(o => o.Progress.AppUserId == userId && o.Progress.PagesRead < o.Series.Pages) - .Select(arg => new - { - Chapter = arg.Chapter, - Progress = arg.Progress, - SeriesId = arg.Series.Id, - SeriesName = arg.Series.Name, - LibraryId = arg.Series.LibraryId, - TotalPages = arg.Series.Pages - }) - .OrderByDescending(d => d.Progress.LastModified) - .Take(limit) - .ToListAsync(); - - return chapters2 - .OrderBy(c => float.Parse(c.Chapter.Number), new ChapterSortComparer()) - .DistinctBy(p => p.SeriesId) - .Select(arg => new InProgressChapterDto() - { - Id = arg.Chapter.Id, - Number = arg.Chapter.Number, - Range = arg.Chapter.Range, - SeriesId = arg.Progress.SeriesId, - SeriesName = arg.SeriesName, - LibraryId = arg.LibraryId, - Pages = arg.Chapter.Pages, - VolumeId = arg.Chapter.VolumeId - }); - - - - // var chapters = await _context.Chapter - // .Join(_context.AppUserProgresses, c => c.Id, p => p.ChapterId, - // (chapter, progress) => - // new - // { - // Chapter = chapter, - // Progress = progress - // }) - // .Join(_context.Series, arg => arg.Progress.SeriesId, series => series.Id, (arg, series) => - // new - // { - // arg.Chapter, - // arg.Progress, - // Series = series, - // VolumeIds = _context.Volume.Where(v => v.SeriesId == series.Id).Select(s => s.Id).ToList() - // }) - // .AsNoTracking() - // .Where(arg => arg.Progress.AppUserId == userId - // && arg.Progress.PagesRead < arg.Chapter.Pages - // && arg.VolumeIds.Contains(arg.Progress.VolumeId)) - // .OrderByDescending(d => d.Progress.LastModified) - // .Take(limit) - // .ToListAsync(); - - // return chapters - // .OrderBy(c => float.Parse(c.Chapter.Number), new ChapterSortComparer()) - // .DistinctBy(p => p.Series.Id) - // .Select(arg => new InProgressChapterDto() - // { - // Id = arg.Chapter.Id, - // Number = arg.Chapter.Number, - // Range = arg.Chapter.Range, - // SeriesId = arg.Progress.SeriesId, - // SeriesName = arg.Series.Name, - // LibraryId = arg.Series.LibraryId, - // Pages = arg.Chapter.Pages, - // }); - } } } \ No newline at end of file diff --git a/API/Entities/MangaFile.cs b/API/Entities/MangaFile.cs index c4471949a..1c0dd4266 100644 --- a/API/Entities/MangaFile.cs +++ b/API/Entities/MangaFile.cs @@ -13,7 +13,7 @@ namespace API.Entities /// /// Number of pages for the given file /// - public int NumberOfPages { get; set; } // TODO: Refactor this to Pages + public int Pages { get; set; } public MangaFormat Format { get; set; } // Relationship Mapping diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs index a9be9e0cb..69e5a4a68 100644 --- a/API/Extensions/ApplicationServiceExtensions.cs +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -32,10 +32,7 @@ namespace API.Extensions services.AddDbContext(options => { - options.UseSqlite(config.GetConnectionString("DefaultConnection"), builder => - { - //builder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - }); + options.UseSqlite(config.GetConnectionString("DefaultConnection")); }); services.AddLogging(loggingBuilder => diff --git a/API/Extensions/LeftJoinExtensions.cs b/API/Extensions/LeftJoinExtensions.cs deleted file mode 100644 index c4ea979a4..000000000 --- a/API/Extensions/LeftJoinExtensions.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; - -namespace API.Extensions -{ - public static class LeftJoinExtensions -{ - public static IQueryable LeftJoin( - this IQueryable outer, - IQueryable inner, - Expression> outerKeySelector, - Expression> innerKeySelector, - Expression> resultSelector) - { - MethodInfo groupJoin = typeof (Queryable).GetMethods() - .Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] GroupJoin[TOuter,TInner,TKey,TResult](System.Linq.IQueryable`1[TOuter], System.Collections.Generic.IEnumerable`1[TInner], System.Linq.Expressions.Expression`1[System.Func`2[TOuter,TKey]], System.Linq.Expressions.Expression`1[System.Func`2[TInner,TKey]], System.Linq.Expressions.Expression`1[System.Func`3[TOuter,System.Collections.Generic.IEnumerable`1[TInner],TResult]])") - .MakeGenericMethod(typeof (TOuter), typeof (TInner), typeof (TKey), typeof (LeftJoinIntermediate)); - MethodInfo selectMany = typeof (Queryable).GetMethods() - .Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] SelectMany[TSource,TCollection,TResult](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,System.Collections.Generic.IEnumerable`1[TCollection]]], System.Linq.Expressions.Expression`1[System.Func`3[TSource,TCollection,TResult]])") - .MakeGenericMethod(typeof (LeftJoinIntermediate), typeof (TInner), typeof (TResult)); - - var groupJoinResultSelector = (Expression, LeftJoinIntermediate>>) - ((oneOuter, manyInners) => new LeftJoinIntermediate {OneOuter = oneOuter, ManyInners = manyInners}); - - MethodCallExpression exprGroupJoin = Expression.Call(groupJoin, outer.Expression, inner.Expression, outerKeySelector, innerKeySelector, groupJoinResultSelector); - - var selectManyCollectionSelector = (Expression, IEnumerable>>) - (t => t.ManyInners.DefaultIfEmpty()); - - ParameterExpression paramUser = resultSelector.Parameters.First(); - - ParameterExpression paramNew = Expression.Parameter(typeof (LeftJoinIntermediate), "t"); - MemberExpression propExpr = Expression.Property(paramNew, "OneOuter"); - - LambdaExpression selectManyResultSelector = Expression.Lambda(new Replacer(paramUser, propExpr).Visit(resultSelector.Body), paramNew, resultSelector.Parameters.Skip(1).First()); - - MethodCallExpression exprSelectMany = Expression.Call(selectMany, exprGroupJoin, selectManyCollectionSelector, selectManyResultSelector); - - return outer.Provider.CreateQuery(exprSelectMany); - } - - private class LeftJoinIntermediate - { - public TOuter OneOuter { get; set; } - public IEnumerable ManyInners { get; set; } - } - - private class Replacer : ExpressionVisitor - { - private readonly ParameterExpression _oldParam; - private readonly Expression _replacement; - - public Replacer(ParameterExpression oldParam, Expression replacement) - { - _oldParam = oldParam; - _replacement = replacement; - } - - public override Expression Visit(Expression exp) - { - if (exp == _oldParam) - { - return _replacement; - } - - return base.Visit(exp); - } - } -} -} \ No newline at end of file diff --git a/API/Interfaces/ISeriesRepository.cs b/API/Interfaces/ISeriesRepository.cs index 647469e69..34a1715dc 100644 --- a/API/Interfaces/ISeriesRepository.cs +++ b/API/Interfaces/ISeriesRepository.cs @@ -18,6 +18,7 @@ namespace API.Interfaces /// /// /// + /// /// Task> GetSeriesDtoForLibraryIdAsync(int libraryId, int userId, UserParams userParams); diff --git a/API/Interfaces/IVolumeRepository.cs b/API/Interfaces/IVolumeRepository.cs index bec554fde..faf18abb8 100644 --- a/API/Interfaces/IVolumeRepository.cs +++ b/API/Interfaces/IVolumeRepository.cs @@ -13,6 +13,5 @@ namespace API.Interfaces Task> GetFilesForChapter(int chapterId); Task> GetChaptersAsync(int volumeId); Task GetChapterCoverImageAsync(int chapterId); - Task> GetContinueReading(int userId, int libraryId, int limit); } } \ No newline at end of file diff --git a/API/Interfaces/Services/IArchiveService.cs b/API/Interfaces/Services/IArchiveService.cs index 65b9d65b9..74f01279b 100644 --- a/API/Interfaces/Services/IArchiveService.cs +++ b/API/Interfaces/Services/IArchiveService.cs @@ -1,7 +1,4 @@ -using System.IO.Compression; -using API.Entities; - -namespace API.Interfaces.Services +namespace API.Interfaces.Services { public interface IArchiveService { diff --git a/API/Interfaces/Services/IDirectoryService.cs b/API/Interfaces/Services/IDirectoryService.cs index d2485e0da..c22fa1189 100644 --- a/API/Interfaces/Services/IDirectoryService.cs +++ b/API/Interfaces/Services/IDirectoryService.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using API.DTOs; namespace API.Interfaces.Services { @@ -20,21 +19,19 @@ namespace API.Interfaces.Services /// /// string[] GetFilesWithExtension(string path, string searchPatternExpression = ""); - //bool ExistOrCreate(string directoryPath); - Task ReadFileAsync(string path); /// /// Deletes all files within the directory, then the directory itself. /// /// - void ClearAndDeleteDirectory(string directoryPath); + //void ClearAndDeleteDirectory(string directoryPath); /// /// Deletes all files within the directory. /// /// /// - void ClearDirectory(string directoryPath); + //void ClearDirectory(string directoryPath); bool CopyFilesToDirectory(IEnumerable filePaths, string directoryPath); bool Exists(string directory); diff --git a/API/Middleware/ExceptionMiddleware.cs b/API/Middleware/ExceptionMiddleware.cs index 8c76b8644..8badfeb96 100644 --- a/API/Middleware/ExceptionMiddleware.cs +++ b/API/Middleware/ExceptionMiddleware.cs @@ -25,14 +25,13 @@ namespace API.Middleware public async Task InvokeAsync(HttpContext context) { - // BUG: I think Hangfire timeouts are triggering the middleware to hijack an API call try { await _next(context); // downstream middlewares or http call } catch (Exception ex) { - _logger.LogError(ex, ex.Message); + _logger.LogError(ex, "There was an exception"); context.Response.ContentType = "application/json"; context.Response.StatusCode = (int) HttpStatusCode.InternalServerError; diff --git a/API/Program.cs b/API/Program.cs index aa6c98e56..ca814beb9 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -2,8 +2,6 @@ using System; using System.Threading.Tasks; using API.Data; using API.Entities; -using API.Interfaces; -using API.Interfaces.Services; using API.Services; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; diff --git a/API/Services/ArchiveService.cs b/API/Services/ArchiveService.cs index d598ab248..4037a3ea5 100644 --- a/API/Services/ArchiveService.cs +++ b/API/Services/ArchiveService.cs @@ -47,9 +47,9 @@ namespace API.Services { try { - _logger.LogDebug("Archive Type: {ArchiveType}", reader.ArchiveType); + _logger.LogDebug("{ArchivePath}'s Type: {ArchiveType}", archivePath, reader.ArchiveType); } - catch (System.InvalidOperationException ex) + catch (InvalidOperationException ex) { _logger.LogError(ex, "Could not parse the archive. Please validate it is not corrupted"); return 0; @@ -86,37 +86,7 @@ namespace API.Services try { if (!IsValidArchive(filepath)) return Array.Empty(); - - // if (SharpCompress.Archives.Zip.ZipArchive.IsZipFile(filepath)) - // { - // using var archive = SharpCompress.Archives.Zip.ZipArchive.Open(filepath); - // return FindCoverImage(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), createThumbnail); - // } - // - // if (GZipArchive.IsGZipFile(filepath)) - // { - // using var archive = GZipArchive.Open(filepath); - // return FindCoverImage(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), createThumbnail); - // } - // - // if (RarArchive.IsRarFile(filepath)) - // { - // using var archive = RarArchive.Open(filepath); - // return FindCoverImage(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), createThumbnail); - // } - // - // if (SevenZipArchive.IsSevenZipFile(filepath)) - // { - // using var archive = SevenZipArchive.Open(filepath); - // return FindCoverImage(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), createThumbnail); - // } - // - // if (TarArchive.IsTarFile(filepath)) - // { - // using var archive = TarArchive.Open(filepath); - // return FindCoverImage(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), createThumbnail); - // } - + using var archive = ArchiveFactory.Open(filepath); return FindCoverImage(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), createThumbnail); } @@ -293,40 +263,11 @@ namespace API.Services { if (!File.Exists(archivePath)) return; + if (new DirectoryInfo(extractPath).Exists) return; + var sw = Stopwatch.StartNew(); - // if (SharpCompress.Archives.Zip.ZipArchive.IsZipFile(archivePath)) - // { - // - // //using var archive = SharpCompress.Archives.Zip.ZipArchive.Open(archivePath); - // using var archive = ArchiveFactory.Open(archivePath); - // ExtractArchiveEntities(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), extractPath); - // } - // else if (GZipArchive.IsGZipFile(archivePath)) - // { - // using var archive = GZipArchive.Open(archivePath); - // ExtractArchiveEntities(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), extractPath); - // } else if (RarArchive.IsRarFile(archivePath)) - // { - // using var archive = RarArchive.Open(archivePath); - // ExtractArchiveEntities(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), extractPath); - // } else if (SevenZipArchive.IsSevenZipFile(archivePath)) - // { - // using var archive = SevenZipArchive.Open(archivePath); - // ExtractArchiveEntities(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), extractPath); - // } - // else if (TarArchive.IsTarFile(archivePath)) - // { - // using var archive = TarArchive.Open(archivePath); - // ExtractArchiveEntities(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), extractPath); - // } - // else - // { - // _logger.LogError("Could not parse archive file"); - // return; - // } using var archive = ArchiveFactory.Open(archivePath); ExtractArchiveEntities(archive.Entries.Where(entry => !entry.IsDirectory && Parser.Parser.IsImage(entry.Key)), extractPath); - _logger.LogDebug("[Fallback] Extracted archive to {ExtractPath} in {ElapsedMilliseconds} milliseconds", extractPath, sw.ElapsedMilliseconds); } } diff --git a/API/Services/CacheService.cs b/API/Services/CacheService.cs index 7be693a41..1a847fdf1 100644 --- a/API/Services/CacheService.cs +++ b/API/Services/CacheService.cs @@ -106,7 +106,7 @@ namespace API.Services var chapterFiles = chapter.Files ?? await _unitOfWork.VolumeRepository.GetFilesForChapter(chapter.Id); foreach (var mangaFile in chapterFiles) { - if (page <= (mangaFile.NumberOfPages + pagesSoFar)) + if (page <= (mangaFile.Pages + pagesSoFar)) { var path = GetCachePath(chapter.Id); var files = _directoryService.GetFilesWithExtension(path, Parser.Parser.ImageFileExtensions); @@ -121,7 +121,7 @@ namespace API.Services return (files.ElementAt(page - pagesSoFar), mangaFile); } - pagesSoFar += mangaFile.NumberOfPages; + pagesSoFar += mangaFile.Pages; } return ("", null); diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index 638d487cc..25f068a62 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -84,13 +84,17 @@ namespace API.Services { Directory.CreateDirectory(directoryPath); } - catch (Exception ex) + catch (Exception) { return false; } return true; } + /// + /// Deletes all files within the directory, then the directory itself. + /// + /// public void ClearAndDeleteDirectory(string directoryPath) { DirectoryInfo di = new DirectoryInfo(directoryPath); @@ -100,6 +104,11 @@ namespace API.Services di.Delete(true); } + /// + /// Deletes all files within the directory. + /// + /// + /// public void ClearDirectory(string directoryPath) { var di = new DirectoryInfo(directoryPath); @@ -239,7 +248,8 @@ namespace API.Services return ++localCount; }, (c) => { - Interlocked.Add(ref fileCount, c); + // ReSharper disable once AccessToModifiedClosure + Interlocked.Add(ref fileCount, c); }); } } diff --git a/API/Services/TaskScheduler.cs b/API/Services/TaskScheduler.cs index 28241aaea..2faf74475 100644 --- a/API/Services/TaskScheduler.cs +++ b/API/Services/TaskScheduler.cs @@ -1,5 +1,4 @@ using System.IO; -using System.Linq; using System.Threading.Tasks; using API.Entities.Enums; using API.Helpers.Converters; @@ -21,17 +20,13 @@ namespace API.Services private readonly IMetadataService _metadataService; private readonly IBackupService _backupService; private readonly ICleanupService _cleanupService; - private readonly IDirectoryService _directoryService; - public static BackgroundJobServer Client => new BackgroundJobServer(new BackgroundJobServerOptions() - { - WorkerCount = 1 - }); + public static BackgroundJobServer Client => new BackgroundJobServer(); public TaskScheduler(ICacheService cacheService, ILogger logger, IScannerService scannerService, IUnitOfWork unitOfWork, IMetadataService metadataService, IBackupService backupService, ICleanupService cleanupService, - IDirectoryService directoryService, IWebHostEnvironment env) + IWebHostEnvironment env) { _cacheService = cacheService; _logger = logger; @@ -40,7 +35,6 @@ namespace API.Services _metadataService = metadataService; _backupService = backupService; _cleanupService = cleanupService; - _directoryService = directoryService; if (!env.IsDevelopment()) { @@ -58,9 +52,8 @@ namespace API.Services public void ScheduleTasks() { _logger.LogInformation("Scheduling reoccurring tasks"); - - string setting = null; - setting = Task.Run(() => _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskScan)).Result.Value; + + string setting = Task.Run(() => _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskScan)).Result.Value; if (setting != null) { _logger.LogDebug("Scheduling Scan Library Task for {Cron}", setting); @@ -87,7 +80,7 @@ namespace API.Services public void ScanLibrary(int libraryId, bool forceUpdate = false) { - + // TODO: We shouldn't queue up a job if one is already in progress _logger.LogInformation("Enqueuing library scan for: {LibraryId}", libraryId); BackgroundJob.Enqueue(() => _scannerService.ScanLibrary(libraryId, forceUpdate)); BackgroundJob.Enqueue(() => _cleanupService.Cleanup()); // When we do a scan, force cache to re-unpack in case page numbers change diff --git a/API/Services/Tasks/BackupService.cs b/API/Services/Tasks/BackupService.cs index e24a8f89e..8e59fce47 100644 --- a/API/Services/Tasks/BackupService.cs +++ b/API/Services/Tasks/BackupService.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; -using System.Threading; using System.Threading.Tasks; using API.Entities.Enums; using API.Extensions; @@ -55,7 +54,7 @@ namespace API.Services.Tasks var files = maxRollingFiles > 0 ? _directoryService.GetFiles(Directory.GetCurrentDirectory(), $@"{fi.Name}{multipleFileRegex}\.log") - : new string[] {"kavita.log"}; + : new[] {"kavita.log"}; return files; } diff --git a/API/Services/Tasks/CleanupService.cs b/API/Services/Tasks/CleanupService.cs index a98e33cbf..484557bcf 100644 --- a/API/Services/Tasks/CleanupService.cs +++ b/API/Services/Tasks/CleanupService.cs @@ -11,14 +11,12 @@ namespace API.Services.Tasks public class CleanupService : ICleanupService { private readonly ICacheService _cacheService; - private readonly IDirectoryService _directoryService; private readonly ILogger _logger; private readonly IBackupService _backupService; - public CleanupService(ICacheService cacheService, IDirectoryService directoryService, ILogger logger, IBackupService backupService) + public CleanupService(ICacheService cacheService, ILogger logger, IBackupService backupService) { _cacheService = cacheService; - _directoryService = directoryService; _logger = logger; _backupService = backupService; } diff --git a/API/Services/Tasks/ScannerService.cs b/API/Services/Tasks/ScannerService.cs index af7b6eca8..686b96dc1 100644 --- a/API/Services/Tasks/ScannerService.cs +++ b/API/Services/Tasks/ScannerService.cs @@ -284,7 +284,7 @@ namespace API.Services.Tasks AddOrUpdateFileForChapter(chapter, info); chapter.Number = Parser.Parser.MinimumNumberFromRange(info.Chapters) + ""; chapter.Range = info.Chapters; - chapter.Pages = chapter.Files.Sum(f => f.NumberOfPages); + chapter.Pages = chapter.Files.Sum(f => f.Pages); _metadataService.UpdateMetadata(chapter, _forceUpdate); } @@ -350,7 +350,7 @@ namespace API.Services.Tasks { FilePath = info.FullFilePath, Format = info.Format, - NumberOfPages = _archiveService.GetNumberOfPagesFromArchive(info.FullFilePath) + Pages = _archiveService.GetNumberOfPagesFromArchive(info.FullFilePath) }; } @@ -361,7 +361,7 @@ namespace API.Services.Tasks if (existingFile != null) { existingFile.Format = info.Format; - existingFile.NumberOfPages = _archiveService.GetNumberOfPagesFromArchive(info.FullFilePath); + existingFile.Pages = _archiveService.GetNumberOfPagesFromArchive(info.FullFilePath); } else {