mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
Some code cleanup
This commit is contained in:
parent
585e965a85
commit
d73bd22db2
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -6,6 +6,7 @@ namespace API.Controllers
|
||||
{
|
||||
public class FallbackController : Controller
|
||||
{
|
||||
// ReSharper disable once S4487
|
||||
private readonly ITaskScheduler _taskScheduler;
|
||||
|
||||
public FallbackController(ITaskScheduler taskScheduler)
|
||||
|
@ -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<ImageController> _logger;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public ImageController(IDirectoryService directoryService, ICacheService cacheService,
|
||||
ILogger<ImageController> logger, IUnitOfWork unitOfWork)
|
||||
public ImageController(IUnitOfWork unitOfWork)
|
||||
{
|
||||
_directoryService = directoryService;
|
||||
_cacheService = cacheService;
|
||||
_logger = logger;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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<ActionResult<IEnumerable<SeriesDto>>> 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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -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<ServerController> 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")]
|
||||
|
@ -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<IEnumerable<string>> GetLogLevels()
|
||||
{
|
||||
return Ok(new string[] {"Trace", "Debug", "Information", "Warning", "Critical", "None"});
|
||||
return Ok(new [] {"Trace", "Debug", "Information", "Warning", "Critical", "None"});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace API.Data
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class BookmarkDto
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
733
API/Data/Migrations/20210322212724_MangaFileToPages.Designer.cs
generated
Normal file
733
API/Data/Migrations/20210322212724_MangaFileToPages.Designer.cs
generated
Normal file
@ -0,0 +1,733 @@
|
||||
// <auto-generated />
|
||||
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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastActive")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HideReadOnDetails")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PageSplitOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReadingDirection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ScalingOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("AppUserPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppUserProgress", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PagesRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("VolumeId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId");
|
||||
|
||||
b.ToTable("AppUserProgresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppUserRating", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Rating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Review")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId");
|
||||
|
||||
b.ToTable("AppUserRating");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppUserRole", b =>
|
||||
{
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("CoverImage")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Number")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Range")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("VolumeId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("VolumeId");
|
||||
|
||||
b.ToTable("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastScanned")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LibraryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
||||
b.ToTable("FolderPath");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Library", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("CoverImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Library");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.MangaFile", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("FilePath")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Format")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.ToTable("MangaFile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Series", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("CoverImage")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LibraryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LocalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("OriginalName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SortName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("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<int>("Key")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("ServerSetting");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Volume", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("CoverImage")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSpecial")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.ToTable("Volume");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AppUserLibrary", b =>
|
||||
{
|
||||
b.Property<int>("AppUsersId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LibrariesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("AppUsersId", "LibrariesId");
|
||||
|
||||
b.HasIndex("LibrariesId");
|
||||
|
||||
b.ToTable("AppUserLibrary");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
|
||||
{
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("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<int>", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", 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
|
||||
}
|
||||
}
|
||||
}
|
23
API/Data/Migrations/20210322212724_MangaFileToPages.cs
Normal file
23
API/Data/Migrations/20210322212724_MangaFileToPages.cs
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -318,7 +318,7 @@ namespace API.Data.Migrations
|
||||
b.Property<int>("Format")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("NumberOfPages")
|
||||
b.Property<int>("Pages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
@ -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
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SeriesDto>> 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
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="limit"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<InProgressChapterDto>> 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,
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ namespace API.Entities
|
||||
/// <summary>
|
||||
/// Number of pages for the given file
|
||||
/// </summary>
|
||||
public int NumberOfPages { get; set; } // TODO: Refactor this to Pages
|
||||
public int Pages { get; set; }
|
||||
public MangaFormat Format { get; set; }
|
||||
|
||||
// Relationship Mapping
|
||||
|
@ -32,10 +32,7 @@ namespace API.Extensions
|
||||
|
||||
services.AddDbContext<DataContext>(options =>
|
||||
{
|
||||
options.UseSqlite(config.GetConnectionString("DefaultConnection"), builder =>
|
||||
{
|
||||
//builder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
|
||||
});
|
||||
options.UseSqlite(config.GetConnectionString("DefaultConnection"));
|
||||
});
|
||||
|
||||
services.AddLogging(loggingBuilder =>
|
||||
|
@ -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<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
|
||||
this IQueryable<TOuter> outer,
|
||||
IQueryable<TInner> inner,
|
||||
Expression<Func<TOuter, TKey>> outerKeySelector,
|
||||
Expression<Func<TInner, TKey>> innerKeySelector,
|
||||
Expression<Func<TOuter, TInner, TResult>> 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<TOuter, TInner>));
|
||||
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<TOuter, TInner>), typeof (TInner), typeof (TResult));
|
||||
|
||||
var groupJoinResultSelector = (Expression<Func<TOuter, IEnumerable<TInner>, LeftJoinIntermediate<TOuter, TInner>>>)
|
||||
((oneOuter, manyInners) => new LeftJoinIntermediate<TOuter, TInner> {OneOuter = oneOuter, ManyInners = manyInners});
|
||||
|
||||
MethodCallExpression exprGroupJoin = Expression.Call(groupJoin, outer.Expression, inner.Expression, outerKeySelector, innerKeySelector, groupJoinResultSelector);
|
||||
|
||||
var selectManyCollectionSelector = (Expression<Func<LeftJoinIntermediate<TOuter, TInner>, IEnumerable<TInner>>>)
|
||||
(t => t.ManyInners.DefaultIfEmpty());
|
||||
|
||||
ParameterExpression paramUser = resultSelector.Parameters.First();
|
||||
|
||||
ParameterExpression paramNew = Expression.Parameter(typeof (LeftJoinIntermediate<TOuter, TInner>), "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<TResult>(exprSelectMany);
|
||||
}
|
||||
|
||||
private class LeftJoinIntermediate<TOuter, TInner>
|
||||
{
|
||||
public TOuter OneOuter { get; set; }
|
||||
public IEnumerable<TInner> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ namespace API.Interfaces
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="userParams"></param>
|
||||
/// <returns></returns>
|
||||
Task<PagedList<SeriesDto>> GetSeriesDtoForLibraryIdAsync(int libraryId, int userId, UserParams userParams);
|
||||
|
||||
|
@ -13,6 +13,5 @@ namespace API.Interfaces
|
||||
Task<IList<MangaFile>> GetFilesForChapter(int chapterId);
|
||||
Task<IList<Chapter>> GetChaptersAsync(int volumeId);
|
||||
Task<byte[]> GetChapterCoverImageAsync(int chapterId);
|
||||
Task<IEnumerable<InProgressChapterDto>> GetContinueReading(int userId, int libraryId, int limit);
|
||||
}
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
using System.IO.Compression;
|
||||
using API.Entities;
|
||||
|
||||
namespace API.Interfaces.Services
|
||||
namespace API.Interfaces.Services
|
||||
{
|
||||
public interface IArchiveService
|
||||
{
|
||||
|
@ -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
|
||||
/// <param name="searchPatternExpression"></param>
|
||||
/// <returns></returns>
|
||||
string[] GetFilesWithExtension(string path, string searchPatternExpression = "");
|
||||
//bool ExistOrCreate(string directoryPath);
|
||||
|
||||
Task<byte[]> ReadFileAsync(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all files within the directory, then the directory itself.
|
||||
/// </summary>
|
||||
/// <param name="directoryPath"></param>
|
||||
void ClearAndDeleteDirectory(string directoryPath);
|
||||
//void ClearAndDeleteDirectory(string directoryPath);
|
||||
/// <summary>
|
||||
/// Deletes all files within the directory.
|
||||
/// </summary>
|
||||
/// <param name="directoryPath"></param>
|
||||
/// <returns></returns>
|
||||
void ClearDirectory(string directoryPath);
|
||||
//void ClearDirectory(string directoryPath);
|
||||
|
||||
bool CopyFilesToDirectory(IEnumerable<string> filePaths, string directoryPath);
|
||||
bool Exists(string directory);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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<byte>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -84,13 +84,17 @@ namespace API.Services
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all files within the directory, then the directory itself.
|
||||
/// </summary>
|
||||
/// <param name="directoryPath"></param>
|
||||
public void ClearAndDeleteDirectory(string directoryPath)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(directoryPath);
|
||||
@ -100,6 +104,11 @@ namespace API.Services
|
||||
di.Delete(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all files within the directory.
|
||||
/// </summary>
|
||||
/// <param name="directoryPath"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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<TaskScheduler> 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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -11,14 +11,12 @@ namespace API.Services.Tasks
|
||||
public class CleanupService : ICleanupService
|
||||
{
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly ILogger<CleanupService> _logger;
|
||||
private readonly IBackupService _backupService;
|
||||
|
||||
public CleanupService(ICacheService cacheService, IDirectoryService directoryService, ILogger<CleanupService> logger, IBackupService backupService)
|
||||
public CleanupService(ICacheService cacheService, ILogger<CleanupService> logger, IBackupService backupService)
|
||||
{
|
||||
_cacheService = cacheService;
|
||||
_directoryService = directoryService;
|
||||
_logger = logger;
|
||||
_backupService = backupService;
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user