diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs index 025017476..6ce1a4636 100644 --- a/API/Controllers/AccountController.cs +++ b/API/Controllers/AccountController.cs @@ -17,20 +17,20 @@ namespace API.Controllers private readonly UserManager _userManager; private readonly SignInManager _signInManager; private readonly ITokenService _tokenService; - private readonly IUserRepository _userRepository; + private readonly IUnitOfWork _unitOfWork; private readonly ILogger _logger; private readonly IMapper _mapper; public AccountController(UserManager userManager, SignInManager signInManager, - ITokenService tokenService, IUserRepository userRepository, + ITokenService tokenService, IUnitOfWork unitOfWork, ILogger logger, IMapper mapper) { _userManager = userManager; _signInManager = signInManager; _tokenService = tokenService; - _userRepository = userRepository; + _unitOfWork = unitOfWork; _logger = logger; _mapper = mapper; } @@ -38,7 +38,6 @@ namespace API.Controllers [HttpPost("register")] public async Task> Register(RegisterDto registerDto) { - if (await _userManager.Users.AnyAsync(x => x.UserName == registerDto.Username)) { return BadRequest("Username is taken."); @@ -77,8 +76,8 @@ namespace API.Controllers // Update LastActive on account user.LastActive = DateTime.Now; - _userRepository.Update(user); - await _userRepository.SaveAllAsync(); + _unitOfWork.UserRepository.Update(user); + await _unitOfWork.Complete(); _logger.LogInformation($"{user.UserName} logged in at {user.LastActive}"); @@ -88,10 +87,5 @@ namespace API.Controllers Token = await _tokenService.CreateToken(user) }; } - - // private async Task UserExists(string username) - // { - // return await _userManager.Users.AnyAsync(user => user.UserName == username.ToLower()); - // } } } \ No newline at end of file diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs index 1cab508b2..56ac7e49f 100644 --- a/API/Controllers/LibraryController.cs +++ b/API/Controllers/LibraryController.cs @@ -18,25 +18,21 @@ namespace API.Controllers public class LibraryController : BaseApiController { private readonly IDirectoryService _directoryService; - private readonly ILibraryRepository _libraryRepository; private readonly ILogger _logger; - private readonly IUserRepository _userRepository; private readonly IMapper _mapper; private readonly ITaskScheduler _taskScheduler; - private readonly ISeriesRepository _seriesRepository; + private readonly IUnitOfWork _unitOfWork; private readonly ICacheService _cacheService; public LibraryController(IDirectoryService directoryService, - ILibraryRepository libraryRepository, ILogger logger, IUserRepository userRepository, - IMapper mapper, ITaskScheduler taskScheduler, ISeriesRepository seriesRepository, ICacheService cacheService) + ILogger logger, IMapper mapper, ITaskScheduler taskScheduler, + IUnitOfWork unitOfWork, ICacheService cacheService) { _directoryService = directoryService; - _libraryRepository = libraryRepository; _logger = logger; - _userRepository = userRepository; _mapper = mapper; _taskScheduler = taskScheduler; - _seriesRepository = seriesRepository; + _unitOfWork = unitOfWork; _cacheService = cacheService; } @@ -49,12 +45,12 @@ namespace API.Controllers [HttpPost("create")] public async Task AddLibrary(CreateLibraryDto createLibraryDto) { - if (await _libraryRepository.LibraryExists(createLibraryDto.Name)) + if (await _unitOfWork.LibraryRepository.LibraryExists(createLibraryDto.Name)) { return BadRequest("Library name already exists. Please choose a unique name to the server."); } - var admins = (await _userRepository.GetAdminUsersAsync()).ToList(); + var admins = (await _unitOfWork.UserRepository.GetAdminUsersAsync()).ToList(); var library = new Library { @@ -72,15 +68,16 @@ namespace API.Controllers } - if (await _userRepository.SaveAllAsync()) + if (!await _unitOfWork.Complete()) { - _logger.LogInformation($"Created a new library: {library.Name}"); - var createdLibrary = await _libraryRepository.GetLibraryForNameAsync(library.Name); - BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(createdLibrary.Id, false)); - return Ok(); + return BadRequest("There was a critical issue. Please try again."); } - return BadRequest("There was a critical issue. Please try again."); + _logger.LogInformation($"Created a new library: {library.Name}"); + var createdLibrary = await _unitOfWork.LibraryRepository.GetLibraryForNameAsync(library.Name); + BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(createdLibrary.Id, false)); + return Ok(); + } /// @@ -105,14 +102,14 @@ namespace API.Controllers [HttpGet] public async Task>> GetLibraries() { - return Ok(await _libraryRepository.GetLibrariesAsync()); + return Ok(await _unitOfWork.LibraryRepository.GetLibrariesAsync()); } [Authorize(Policy = "RequireAdminRole")] [HttpPut("update-for")] public async Task> AddLibraryToUser(UpdateLibraryForUserDto updateLibraryForUserDto) { - var user = await _userRepository.GetUserByUsernameAsync(updateLibraryForUserDto.Username); + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(updateLibraryForUserDto.Username); if (user == null) return BadRequest("Could not validate user"); @@ -123,7 +120,7 @@ namespace API.Controllers user.Libraries.Add(_mapper.Map(selectedLibrary)); } - if (await _userRepository.SaveAllAsync()) + if (await _unitOfWork.Complete()) { _logger.LogInformation($"Added: {updateLibraryForUserDto.SelectedLibraries} to {updateLibraryForUserDto.Username}"); return Ok(user); @@ -143,7 +140,7 @@ namespace API.Controllers [HttpGet("libraries-for")] public async Task>> GetLibrariesForUser(string username) { - return Ok(await _libraryRepository.GetLibrariesDtoForUsernameAsync(username)); + return Ok(await _unitOfWork.LibraryRepository.GetLibrariesDtoForUsernameAsync(username)); } [HttpGet("series")] @@ -151,10 +148,10 @@ namespace API.Controllers { if (forUser) { - var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername()); - return Ok(await _seriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, user.Id)); + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); + return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, user.Id)); } - return Ok(await _seriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId)); + return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId)); } [Authorize(Policy = "RequireAdminRole")] @@ -163,10 +160,10 @@ namespace API.Controllers { var username = User.GetUsername(); _logger.LogInformation($"Library {libraryId} is being deleted by {username}."); - var series = await _seriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId); - var volumes = (await _seriesRepository.GetVolumesForSeriesAsync(series.Select(x => x.Id).ToArray())) + var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId); + var volumes = (await _unitOfWork.SeriesRepository.GetVolumesForSeriesAsync(series.Select(x => x.Id).ToArray())) .Select(x => x.Id).ToArray(); - var result = await _libraryRepository.DeleteLibrary(libraryId); + var result = await _unitOfWork.LibraryRepository.DeleteLibrary(libraryId); if (result && volumes.Any()) { @@ -180,7 +177,7 @@ namespace API.Controllers [HttpPost("update")] public async Task UpdateLibrary(UpdateLibraryDto libraryForUserDto) { - var library = await _libraryRepository.GetLibraryForIdAsync(libraryForUserDto.Id); + var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryForUserDto.Id); var originalFolders = library.Folders.Select(x => x.Path); var differenceBetweenFolders = originalFolders.Except(libraryForUserDto.Folders); @@ -190,9 +187,9 @@ namespace API.Controllers - _libraryRepository.Update(library); + _unitOfWork.LibraryRepository.Update(library); - if (await _libraryRepository.SaveAllAsync()) + if (await _unitOfWork.Complete()) { if (differenceBetweenFolders.Any()) { diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs index 370fe071f..59bb517f3 100644 --- a/API/Controllers/ReaderController.cs +++ b/API/Controllers/ReaderController.cs @@ -6,9 +6,7 @@ using API.DTOs; using API.Entities; using API.Extensions; using API.Interfaces; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace API.Controllers @@ -18,22 +16,15 @@ namespace API.Controllers private readonly IDirectoryService _directoryService; private readonly ICacheService _cacheService; private readonly ILogger _logger; - private readonly UserManager _userManager; - private readonly DataContext _dataContext; // TODO: Refactor code into repo - private readonly IUserRepository _userRepository; - private readonly ISeriesRepository _seriesRepository; + private readonly IUnitOfWork _unitOfWork; public ReaderController(IDirectoryService directoryService, ICacheService cacheService, - ILogger logger, UserManager userManager, DataContext dataContext, - IUserRepository userRepository, ISeriesRepository seriesRepository) + ILogger logger, IUnitOfWork unitOfWork) { _directoryService = directoryService; _cacheService = cacheService; _logger = logger; - _userManager = userManager; - _dataContext = dataContext; - _userRepository = userRepository; - _seriesRepository = seriesRepository; + _unitOfWork = unitOfWork; } [HttpGet("image")] @@ -52,7 +43,7 @@ namespace API.Controllers [HttpGet("get-bookmark")] public async Task> GetBookmark(int volumeId) { - var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername()); + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); if (user.Progresses == null) return Ok(0); var progress = user.Progresses.SingleOrDefault(x => x.AppUserId == user.Id && x.VolumeId == volumeId); @@ -64,7 +55,7 @@ namespace API.Controllers [HttpPost("bookmark")] public async Task Bookmark(BookmarkDto bookmarkDto) { - var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername()); + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); _logger.LogInformation($"Saving {user.UserName} progress for {bookmarkDto.VolumeId} to page {bookmarkDto.PageNum}"); user.Progresses ??= new List(); @@ -87,9 +78,9 @@ namespace API.Controllers } - _userRepository.Update(user); + _unitOfWork.UserRepository.Update(user); - if (await _userRepository.SaveAllAsync()) + if (await _unitOfWork.Complete()) { return Ok(); } diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs index c8eee5db5..f93f4700b 100644 --- a/API/Controllers/SeriesController.cs +++ b/API/Controllers/SeriesController.cs @@ -15,28 +15,23 @@ namespace API.Controllers public class SeriesController : BaseApiController { private readonly ILogger _logger; - private readonly IMapper _mapper; private readonly ITaskScheduler _taskScheduler; - private readonly ISeriesRepository _seriesRepository; private readonly ICacheService _cacheService; - private readonly IUserRepository _userRepository; + private readonly IUnitOfWork _unitOfWork; - public SeriesController(ILogger logger, IMapper mapper, - ITaskScheduler taskScheduler, ISeriesRepository seriesRepository, - ICacheService cacheService, IUserRepository userRepository) + public SeriesController(ILogger logger, ITaskScheduler taskScheduler, + ICacheService cacheService, IUnitOfWork unitOfWork) { _logger = logger; - _mapper = mapper; _taskScheduler = taskScheduler; - _seriesRepository = seriesRepository; _cacheService = cacheService; - _userRepository = userRepository; + _unitOfWork = unitOfWork; } [HttpGet("{seriesId}")] public async Task> GetSeries(int seriesId) { - return Ok(await _seriesRepository.GetSeriesDtoByIdAsync(seriesId)); + return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId)); } [Authorize(Policy = "RequireAdminRole")] @@ -44,9 +39,9 @@ namespace API.Controllers public async Task> DeleteSeries(int seriesId) { var username = User.GetUsername(); - var volumes = (await _seriesRepository.GetVolumesForSeriesAsync(new []{seriesId})).Select(x => x.Id).ToArray(); + var volumes = (await _unitOfWork.SeriesRepository.GetVolumesForSeriesAsync(new []{seriesId})).Select(x => x.Id).ToArray(); _logger.LogInformation($"Series {seriesId} is being deleted by {username}."); - var result = await _seriesRepository.DeleteSeriesAsync(seriesId); + var result = await _unitOfWork.SeriesRepository.DeleteSeriesAsync(seriesId); if (result) { @@ -60,16 +55,16 @@ namespace API.Controllers { if (forUser) { - var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername()); - return Ok(await _seriesRepository.GetVolumesDtoAsync(seriesId, user.Id)); + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); + return Ok(await _unitOfWork.SeriesRepository.GetVolumesDtoAsync(seriesId, user.Id)); } - return Ok(await _seriesRepository.GetVolumesDtoAsync(seriesId)); // TODO: Refactor out forUser = false since everything is user based + return Ok(await _unitOfWork.SeriesRepository.GetVolumesDtoAsync(seriesId)); // TODO: Refactor out forUser = false since everything is user based } [HttpGet("volume")] public async Task> GetVolume(int volumeId) { - return Ok(await _seriesRepository.GetVolumeDtoAsync(volumeId)); + return Ok(await _unitOfWork.SeriesRepository.GetVolumeDtoAsync(volumeId)); } } } \ No newline at end of file diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index c3ca25288..07c3570f8 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -12,23 +12,21 @@ namespace API.Controllers [Authorize] public class UsersController : BaseApiController { - private readonly IUserRepository _userRepository; - private readonly ILibraryRepository _libraryRepository; + private readonly IUnitOfWork _unitOfWork; - public UsersController(IUserRepository userRepository, ILibraryRepository libraryRepository) + public UsersController(IUnitOfWork unitOfWork) { - _userRepository = userRepository; - _libraryRepository = libraryRepository; + _unitOfWork = unitOfWork; } [Authorize(Policy = "RequireAdminRole")] [HttpDelete("delete-user")] public async Task DeleteUser(string username) { - var user = await _userRepository.GetUserByUsernameAsync(username); - _userRepository.Delete(user); + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username); + _unitOfWork.UserRepository.Delete(user); - if (await _userRepository.SaveAllAsync()) + if (await _unitOfWork.Complete()) { return Ok(); } @@ -40,7 +38,7 @@ namespace API.Controllers [HttpGet] public async Task>> GetUsers() { - return Ok(await _userRepository.GetMembersAsync()); + return Ok(await _unitOfWork.UserRepository.GetMembersAsync()); } [HttpGet("has-library-access")] @@ -48,11 +46,11 @@ namespace API.Controllers { // TODO: refactor this to use either userexists or usermanager - var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername()); + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); if (user == null) return BadRequest("Could not validate user"); - var libs = await _libraryRepository.GetLibrariesDtoForUsernameAsync(user.UserName); + var libs = await _unitOfWork.LibraryRepository.GetLibrariesDtoForUsernameAsync(user.UserName); return Ok(libs.Any(x => x.Id == libraryId)); } diff --git a/API/DTOs/SeriesDto.cs b/API/DTOs/SeriesDto.cs index 942acc2f1..50160ab5c 100644 --- a/API/DTOs/SeriesDto.cs +++ b/API/DTOs/SeriesDto.cs @@ -2,17 +2,16 @@ { public class SeriesDto { - public int Id { get; set; } - public string Name { get; set; } - public string OriginalName { get; set; } - public string SortName { get; set; } - public string Summary { get; set; } - public byte[] CoverImage { get; set; } - - // Read Progress - public int Pages { get; set; } + public int Id { get; init; } + public string Name { get; init; } + public string OriginalName { get; init; } + public string SortName { get; init; } + public string Summary { get; init; } + public byte[] CoverImage { get; init; } + public int Pages { get; init; } + /// + /// Sum of pages read from linked Volumes. Calculated at API-time. + /// public int PagesRead { get; set; } - //public int VolumesComplete { get; set; } - //public int TotalVolumes { get; set; } } } \ No newline at end of file diff --git a/API/Data/UnitOfWork.cs b/API/Data/UnitOfWork.cs new file mode 100644 index 000000000..77f461f7b --- /dev/null +++ b/API/Data/UnitOfWork.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using API.Entities; +using API.Interfaces; +using AutoMapper; +using Microsoft.AspNetCore.Identity; + +namespace API.Data +{ + public class UnitOfWork : IUnitOfWork + { + private readonly DataContext _context; + private readonly IMapper _mapper; + private readonly UserManager _userManager; + + public UnitOfWork(DataContext context, IMapper mapper, UserManager userManager) + { + _context = context; + _mapper = mapper; + _userManager = userManager; + } + + public ISeriesRepository SeriesRepository => new SeriesRepository(_context, _mapper); + public IUserRepository UserRepository => new UserRepository(_context, _mapper, _userManager); + public ILibraryRepository LibraryRepository => new LibraryRepository(_context, _mapper); + + public async Task Complete() + { + return await _context.SaveChangesAsync() > 0; + } + + public bool HasChanges() + { + return _context.ChangeTracker.HasChanges(); + } + } +} \ No newline at end of file diff --git a/API/Entities/AppUser.cs b/API/Entities/AppUser.cs index 46480fdd1..10bcec503 100644 --- a/API/Entities/AppUser.cs +++ b/API/Entities/AppUser.cs @@ -12,11 +12,7 @@ namespace API.Entities public DateTime Created { get; set; } = DateTime.Now; public DateTime LastActive { get; set; } public ICollection Libraries { get; set; } - - public ICollection UserRoles { get; set; } - - //public ICollection SeriesProgresses { get; set; } public ICollection Progresses { get; set; } [ConcurrencyCheck] diff --git a/API/Entities/AppUserProgress.cs b/API/Entities/AppUserProgress.cs index e9d6a2279..5c8d2c05c 100644 --- a/API/Entities/AppUserProgress.cs +++ b/API/Entities/AppUserProgress.cs @@ -7,10 +7,11 @@ { public int Id { get; set; } public int PagesRead { get; set; } - - public AppUser AppUser { get; set; } - public int AppUserId { get; set; } public int VolumeId { get; set; } public int SeriesId { get; set; } + + // Relationships + public AppUser AppUser { get; set; } + public int AppUserId { get; set; } } } \ No newline at end of file diff --git a/API/Entities/LibraryType.cs b/API/Entities/LibraryType.cs index 6136042b0..6061e3e8f 100644 --- a/API/Entities/LibraryType.cs +++ b/API/Entities/LibraryType.cs @@ -9,8 +9,6 @@ namespace API.Entities [Description("Comic")] Comic = 1, [Description("Book")] - Book = 2, - [Description("Raw")] - Raw = 3 + Book = 2 } } \ No newline at end of file diff --git a/API/Entities/MangaFile.cs b/API/Entities/MangaFile.cs index b93c128a3..4c0c675de 100644 --- a/API/Entities/MangaFile.cs +++ b/API/Entities/MangaFile.cs @@ -4,9 +4,12 @@ namespace API.Entities public class MangaFile { public int Id { get; set; } + /// + /// Absolute path to the archive file + /// public string FilePath { get; set; } /// - /// Do not expect this to be set. If this MangaFile represents a volume file, this will be null. + /// Used to track if multiple MangaFiles (archives) represent a single Volume. If only one volume file, this will be 0. /// public int Chapter { get; set; } /// diff --git a/API/Entities/Series.cs b/API/Entities/Series.cs index 3fc8c0b72..8fe8e6628 100644 --- a/API/Entities/Series.cs +++ b/API/Entities/Series.cs @@ -30,10 +30,8 @@ namespace API.Entities /// Sum of all Volume pages /// public int Pages { get; set; } - /// - /// Total Volumes linked to Entity - /// - //public int TotalVolumes { get; set; } + + // Relationships public ICollection Volumes { get; set; } public Library Library { get; set; } public int LibraryId { get; set; } diff --git a/API/Entities/Volume.cs b/API/Entities/Volume.cs index 8239792f9..304c2bfae 100644 --- a/API/Entities/Volume.cs +++ b/API/Entities/Volume.cs @@ -17,7 +17,7 @@ namespace API.Entities - // Many-to-One relationships + // Relationships public Series Series { get; set; } public int SeriesId { get; set; } } diff --git a/API/Errors/ApiException.cs b/API/Errors/ApiException.cs index 3f026bb64..ce1792f72 100644 --- a/API/Errors/ApiException.cs +++ b/API/Errors/ApiException.cs @@ -2,9 +2,9 @@ { public class ApiException { - public int Status { get; set; } - public string Message { get; set; } - public string Details { get; set; } + public int Status { get; init; } + public string Message { get; init; } + public string Details { get; init; } public ApiException(int status, string message = null, string details = null) { diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs index 63d6a04a7..fba75e148 100644 --- a/API/Extensions/ApplicationServiceExtensions.cs +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -17,14 +17,13 @@ namespace API.Extensions { services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - + services.AddScoped(); + + + services.AddDbContext(options => { options.UseSqlite(config.GetConnectionString("DefaultConnection")); diff --git a/API/Interfaces/IDirectoryService.cs b/API/Interfaces/IDirectoryService.cs index 9592292c4..cb007b1a5 100644 --- a/API/Interfaces/IDirectoryService.cs +++ b/API/Interfaces/IDirectoryService.cs @@ -13,13 +13,8 @@ namespace API.Interfaces /// List of folder names IEnumerable ListDirectory(string rootPath); - /// - /// Lists out top-level files for a given directory. - /// TODO: Implement ability to provide a filter for file types (done in another implementation on DirectoryService) - /// - /// Absolute path - /// List of folder names - IList ListFiles(string rootPath); + + //IList ListFiles(string rootPath); /// /// Given a library id, scans folders for said library. Parses files and generates DB updates. Will overwrite diff --git a/API/Interfaces/ILibraryRepository.cs b/API/Interfaces/ILibraryRepository.cs index 5a919770c..5050c255b 100644 --- a/API/Interfaces/ILibraryRepository.cs +++ b/API/Interfaces/ILibraryRepository.cs @@ -8,7 +8,6 @@ namespace API.Interfaces public interface ILibraryRepository { void Update(Library library); - Task SaveAllAsync(); Task> GetLibrariesAsync(); Task LibraryExists(string libraryName); Task GetLibraryForIdAsync(int libraryId); diff --git a/API/Interfaces/ISeriesRepository.cs b/API/Interfaces/ISeriesRepository.cs index 3657a2a6e..d2fe75353 100644 --- a/API/Interfaces/ISeriesRepository.cs +++ b/API/Interfaces/ISeriesRepository.cs @@ -8,17 +8,15 @@ namespace API.Interfaces public interface ISeriesRepository { void Update(Series series); - Task SaveAllAsync(); Task GetSeriesByNameAsync(string name); Series GetSeriesByName(string name); - bool SaveAll(); Task> GetSeriesDtoForLibraryIdAsync(int libraryId, int userId = 0); Task> GetVolumesDtoAsync(int seriesId, int userId = 0); IEnumerable GetVolumes(int seriesId); Task GetSeriesDtoByIdAsync(int seriesId); Task GetVolumeAsync(int volumeId); - Task GetVolumeDtoAsync(int volumeId); // TODO: Likely need to update here + Task GetVolumeDtoAsync(int volumeId); Task> GetVolumesForSeriesAsync(int[] seriesIds); Task DeleteSeriesAsync(int seriesId); diff --git a/API/Interfaces/IUnitOfWork.cs b/API/Interfaces/IUnitOfWork.cs new file mode 100644 index 000000000..3b1cf4347 --- /dev/null +++ b/API/Interfaces/IUnitOfWork.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace API.Interfaces +{ + public interface IUnitOfWork + { + ISeriesRepository SeriesRepository { get; } + IUserRepository UserRepository { get; } + ILibraryRepository LibraryRepository { get; } + Task Complete(); + bool HasChanges(); + } +} \ No newline at end of file diff --git a/API/Interfaces/IUserRepository.cs b/API/Interfaces/IUserRepository.cs index 26e3b5a11..43232e9cc 100644 --- a/API/Interfaces/IUserRepository.cs +++ b/API/Interfaces/IUserRepository.cs @@ -8,12 +8,8 @@ namespace API.Interfaces public interface IUserRepository { void Update(AppUser user); - Task SaveAllAsync(); - Task> GetUsersAsync(); - Task GetUserByIdAsync(int id); Task GetUserByUsernameAsync(string username); Task> GetMembersAsync(); - Task GetMemberAsync(string username); public void Delete(AppUser user); Task> GetAdminUsersAsync(); } diff --git a/API/Services/CacheService.cs b/API/Services/CacheService.cs index a6372bb1f..3572d7567 100644 --- a/API/Services/CacheService.cs +++ b/API/Services/CacheService.cs @@ -13,16 +13,16 @@ namespace API.Services public class CacheService : ICacheService { private readonly IDirectoryService _directoryService; - private readonly ISeriesRepository _seriesRepository; private readonly ILogger _logger; + private readonly IUnitOfWork _unitOfWork; private readonly NumericComparer _numericComparer; private readonly string _cacheDirectory = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "../cache/")); - public CacheService(IDirectoryService directoryService, ISeriesRepository seriesRepository, ILogger logger) + public CacheService(IDirectoryService directoryService, ILogger logger, IUnitOfWork unitOfWork) { _directoryService = directoryService; - _seriesRepository = seriesRepository; _logger = logger; + _unitOfWork = unitOfWork; _numericComparer = new NumericComparer(); } @@ -38,7 +38,7 @@ namespace API.Services { return null; } - Volume volume = await _seriesRepository.GetVolumeAsync(volumeId); + Volume volume = await _unitOfWork.SeriesRepository.GetVolumeAsync(volumeId); foreach (var file in volume.Files) { var extractPath = GetVolumeCachePath(volumeId, file); @@ -109,8 +109,7 @@ namespace API.Services if (page + 1 < (mangaFile.NumberOfPages + pagesSoFar)) { var path = GetVolumeCachePath(volume.Id, mangaFile); - - var files = _directoryService.ListFiles(path); + var files = DirectoryService.GetFiles(path); var array = files.ToArray(); Array.Sort(array, _numericComparer); // TODO: Find a way to apply numericComparer to IList. diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index 5828b507a..d76dfd65b 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -23,31 +23,28 @@ namespace API.Services public class DirectoryService : IDirectoryService { private readonly ILogger _logger; - private readonly ISeriesRepository _seriesRepository; - private readonly ILibraryRepository _libraryRepository; + private readonly IUnitOfWork _unitOfWork; private ConcurrentDictionary> _scannedSeries; - public DirectoryService(ILogger logger, - ISeriesRepository seriesRepository, - ILibraryRepository libraryRepository) + public DirectoryService(ILogger logger, IUnitOfWork unitOfWork) { _logger = logger; - _seriesRepository = seriesRepository; - _libraryRepository = libraryRepository; + _unitOfWork = unitOfWork; } /// /// Given a set of regex search criteria, get files in the given path. /// /// Directory to search - /// Regex version of search pattern (ie \.mp3|\.mp4) + /// Regex version of search pattern (ie \.mp3|\.mp4). Defaults to * meaning all files. /// SearchOption to use, defaults to TopDirectoryOnly /// List of file paths - private static IEnumerable GetFiles(string path, - string searchPatternExpression = "", + public static IEnumerable GetFiles(string path, + string searchPatternExpression = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) { + if (!Directory.Exists(path)) return ImmutableList.Empty; var reSearchPattern = new Regex(searchPatternExpression, RegexOptions.IgnoreCase); return Directory.EnumerateFiles(path, "*", searchOption) .Where(file => @@ -67,11 +64,11 @@ namespace API.Services return dirs; } - public IList ListFiles(string rootPath) - { - if (!Directory.Exists(rootPath)) return ImmutableList.Empty; - return Directory.GetFiles(rootPath); - } + // public IList ListFiles(string rootPath) + // { + // if (!Directory.Exists(rootPath)) return ImmutableList.Empty; + // return Directory.GetFiles(rootPath); + // } /// @@ -114,7 +111,7 @@ namespace API.Services private Series UpdateSeries(string seriesName, ParserInfo[] infos, bool forceUpdate) { - var series = _seriesRepository.GetSeriesByName(seriesName) ?? new Series + var series = _unitOfWork.SeriesRepository.GetSeriesByName(seriesName) ?? new Series { Name = seriesName, OriginalName = seriesName, @@ -126,7 +123,7 @@ namespace API.Services series.Volumes = volumes; series.CoverImage = volumes.OrderBy(x => x.Number).FirstOrDefault()?.CoverImage; series.Pages = volumes.Sum(v => v.Pages); - //series.TotalVolumes = volumes.Count; + return series; } @@ -154,7 +151,7 @@ namespace API.Services private ICollection UpdateVolumes(Series series, ParserInfo[] infos, bool forceUpdate) { ICollection volumes = new List(); - IList existingVolumes = _seriesRepository.GetVolumes(series.Id).ToList(); + IList existingVolumes = _unitOfWork.SeriesRepository.GetVolumes(series.Id).ToList(); foreach (var info in infos) { @@ -220,7 +217,7 @@ namespace API.Services Library library; try { - library = Task.Run(() => _libraryRepository.GetLibraryForIdAsync(libraryId)).Result; + library = Task.Run(() => _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId)).Result; } catch (Exception ex) { @@ -264,12 +261,10 @@ namespace API.Services _logger.LogInformation($"Created/Updated series {mangaSeries.Name}"); library.Series.Add(mangaSeries); } + + _unitOfWork.LibraryRepository.Update(library); - - - _libraryRepository.Update(library); - - if (_libraryRepository.SaveAll()) + if (Task.Run(() => _unitOfWork.Complete()).Result) { _logger.LogInformation($"Scan completed on {library.Name}. Parsed {series.Keys.Count()} series."); } @@ -287,46 +282,6 @@ namespace API.Services return Path.Join(Directory.GetCurrentDirectory(), $"../cache/{volumeId}/"); } - /// - /// TODO: Delete this method - /// - /// - /// - /// - private string ExtractArchive(string archivePath, int volumeId) - { - if (!File.Exists(archivePath) || !Parser.Parser.IsArchive(archivePath)) - { - _logger.LogError($"Archive {archivePath} could not be found."); - return ""; - } - - var extractPath = GetExtractPath(volumeId); - - if (Directory.Exists(extractPath)) - { - _logger.LogInformation($"Archive {archivePath} has already been extracted. Returning existing folder."); - return extractPath; - } - - using ZipArchive archive = ZipFile.OpenRead(archivePath); - - // TODO: Throw error if we couldn't extract - var needsFlattening = archive.Entries.Count > 0 && !Path.HasExtension(archive.Entries.ElementAt(0).FullName); - if (!archive.HasFiles() && !needsFlattening) return ""; - - archive.ExtractToDirectory(extractPath); - _logger.LogInformation($"Extracting archive to {extractPath}"); - - if (needsFlattening) - { - _logger.LogInformation("Extracted archive is nested in root folder, flattening..."); - new DirectoryInfo(extractPath).Flatten(); - } - - return extractPath; - } - public string ExtractArchive(string archivePath, string extractPath) { if (!File.Exists(archivePath) || !Parser.Parser.IsArchive(archivePath)) diff --git a/API/Services/TaskScheduler.cs b/API/Services/TaskScheduler.cs index 00fd5597e..cb89df4d8 100644 --- a/API/Services/TaskScheduler.cs +++ b/API/Services/TaskScheduler.cs @@ -16,6 +16,7 @@ namespace API.Services _logger.LogInformation("Scheduling/Updating cache cleanup on a daily basis."); RecurringJob.AddOrUpdate(() => cacheService.Cleanup(), Cron.Daily); + //RecurringJob.AddOrUpdate(() => scanService.ScanLibraries(), Cron.Daily); }