mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
* Some performance refactoring around getting Library and avoid a byte[] copy for getting cover images for epubs. * Initial commit. Rewrote the main series scan loop to use chunks of data at a time. Not fully shaken out. * Hooked in the ability for the UI to react to series being added or removed from the DB. * Cleaned up the messaging in the scan loop to be more clear. * Metadata scan and scan work as expected and populate data to the UI. There is a slow down in speed for overall operation. Scan series and refresh series metadata does not work fully. * Fixed a bug where MangaFiles were not having LastModified Updated correctly, meaning they were opening archives every scan. * Modified the code to be more realistic to the underlying file * Updated ScanService to properly handle deleted files and not result in a higher-level scan. * Shuffled around volume related repo apis to the volume repo rather than being in series. * Rewrote scan series to be much cleaner and more concise on the flow. Fixed an issue in UpdateVolumes such that the debug code to log out removed volumes could throw an exception and actually break updating volumes. * Refactored the code to set MangaFile last modified timestamp into the MangaFile entity. * Added Series Name to ScanSeries event * Added additional checks in ScanSeries to ensure we never go outside the library folder. Added extra debug messages for when a metadata refresh doesn't actually make changes and for when we regen cover images. * More logging statements saying where they originate from. Fixed a critical bug which caused only 1 chunk to ever be processed. * Fixed a concurrency issue with natural sorter which could cause issues in ArchiveService.cs. * Log cleanups * Fixed an issue with logging out total time of a scan. * Only show added toastrs for admins. When kicking off a refresh metadata for series, make sure we regenerate all cover images. * Code smells on benchmark despite it being ignored
249 lines
9.5 KiB
C#
249 lines
9.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using API.Data.Repositories;
|
|
using API.DTOs;
|
|
using API.Entities;
|
|
using API.Entities.Enums;
|
|
using API.Extensions;
|
|
using API.Interfaces;
|
|
using API.Interfaces.Services;
|
|
using AutoMapper;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace API.Controllers
|
|
{
|
|
[Authorize]
|
|
public class LibraryController : BaseApiController
|
|
{
|
|
private readonly IDirectoryService _directoryService;
|
|
private readonly ILogger<LibraryController> _logger;
|
|
private readonly IMapper _mapper;
|
|
private readonly ITaskScheduler _taskScheduler;
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
|
|
public LibraryController(IDirectoryService directoryService,
|
|
ILogger<LibraryController> logger, IMapper mapper, ITaskScheduler taskScheduler,
|
|
IUnitOfWork unitOfWork)
|
|
{
|
|
_directoryService = directoryService;
|
|
_logger = logger;
|
|
_mapper = mapper;
|
|
_taskScheduler = taskScheduler;
|
|
_unitOfWork = unitOfWork;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new Library. Upon library creation, adds new library to all Admin accounts.
|
|
/// </summary>
|
|
/// <param name="createLibraryDto"></param>
|
|
/// <returns></returns>
|
|
[Authorize(Policy = "RequireAdminRole")]
|
|
[HttpPost("create")]
|
|
public async Task<ActionResult> AddLibrary(CreateLibraryDto createLibraryDto)
|
|
{
|
|
if (await _unitOfWork.LibraryRepository.LibraryExists(createLibraryDto.Name))
|
|
{
|
|
return BadRequest("Library name already exists. Please choose a unique name to the server.");
|
|
}
|
|
|
|
var library = new Library
|
|
{
|
|
Name = createLibraryDto.Name,
|
|
Type = createLibraryDto.Type,
|
|
Folders = createLibraryDto.Folders.Select(x => new FolderPath {Path = x}).ToList()
|
|
};
|
|
|
|
_unitOfWork.LibraryRepository.Add(library);
|
|
|
|
var admins = (await _unitOfWork.UserRepository.GetAdminUsersAsync()).ToList();
|
|
foreach (var admin in admins)
|
|
{
|
|
admin.Libraries ??= new List<Library>();
|
|
admin.Libraries.Add(library);
|
|
}
|
|
|
|
|
|
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was a critical issue. Please try again.");
|
|
|
|
_logger.LogInformation("Created a new library: {LibraryName}", library.Name);
|
|
_taskScheduler.ScanLibrary(library.Id);
|
|
return Ok();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a list of directories for a given path. If path is empty, returns root drives.
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
/// <returns></returns>
|
|
[Authorize(Policy = "RequireAdminRole")]
|
|
[HttpGet("list")]
|
|
public ActionResult<IEnumerable<string>> GetDirectories(string path)
|
|
{
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
return Ok(Directory.GetLogicalDrives());
|
|
}
|
|
|
|
if (!Directory.Exists(path)) return BadRequest("This is not a valid path");
|
|
|
|
return Ok(_directoryService.ListDirectory(path));
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<ActionResult<IEnumerable<LibraryDto>>> GetLibraries()
|
|
{
|
|
return Ok(await _unitOfWork.LibraryRepository.GetLibraryDtosAsync());
|
|
}
|
|
|
|
[Authorize(Policy = "RequireAdminRole")]
|
|
[HttpPost("grant-access")]
|
|
public async Task<ActionResult<MemberDto>> UpdateUserLibraries(UpdateLibraryForUserDto updateLibraryForUserDto)
|
|
{
|
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(updateLibraryForUserDto.Username);
|
|
if (user == null) return BadRequest("Could not validate user");
|
|
|
|
var libraryString = String.Join(",", updateLibraryForUserDto.SelectedLibraries.Select(x => x.Name));
|
|
_logger.LogInformation("Granting user {UserName} access to: {Libraries}", updateLibraryForUserDto.Username, libraryString);
|
|
|
|
var allLibraries = await _unitOfWork.LibraryRepository.GetLibrariesAsync();
|
|
foreach (var library in allLibraries)
|
|
{
|
|
library.AppUsers ??= new List<AppUser>();
|
|
var libraryContainsUser = library.AppUsers.Any(u => u.UserName == user.UserName);
|
|
var libraryIsSelected = updateLibraryForUserDto.SelectedLibraries.Any(l => l.Id == library.Id);
|
|
if (libraryContainsUser && !libraryIsSelected)
|
|
{
|
|
// Remove
|
|
library.AppUsers.Remove(user);
|
|
}
|
|
else if (!libraryContainsUser && libraryIsSelected)
|
|
{
|
|
library.AppUsers.Add(user);
|
|
}
|
|
|
|
}
|
|
|
|
if (!_unitOfWork.HasChanges())
|
|
{
|
|
_logger.LogInformation("Added: {SelectedLibraries} to {Username}",libraryString, updateLibraryForUserDto.Username);
|
|
return Ok(_mapper.Map<MemberDto>(user));
|
|
}
|
|
|
|
if (await _unitOfWork.CommitAsync())
|
|
{
|
|
_logger.LogInformation("Added: {SelectedLibraries} to {Username}",libraryString, updateLibraryForUserDto.Username);
|
|
return Ok(_mapper.Map<MemberDto>(user));
|
|
}
|
|
|
|
|
|
return BadRequest("There was a critical issue. Please try again.");
|
|
}
|
|
|
|
[Authorize(Policy = "RequireAdminRole")]
|
|
[HttpPost("scan")]
|
|
public ActionResult Scan(int libraryId)
|
|
{
|
|
_taskScheduler.ScanLibrary(libraryId);
|
|
return Ok();
|
|
}
|
|
|
|
[Authorize(Policy = "RequireAdminRole")]
|
|
[HttpPost("refresh-metadata")]
|
|
public ActionResult RefreshMetadata(int libraryId)
|
|
{
|
|
_taskScheduler.RefreshMetadata(libraryId);
|
|
return Ok();
|
|
}
|
|
|
|
[HttpGet("libraries")]
|
|
public async Task<ActionResult<IEnumerable<LibraryDto>>> GetLibrariesForUser()
|
|
{
|
|
return Ok(await _unitOfWork.LibraryRepository.GetLibraryDtosForUsernameAsync(User.GetUsername()));
|
|
}
|
|
|
|
[Authorize(Policy = "RequireAdminRole")]
|
|
[HttpDelete("delete")]
|
|
public async Task<ActionResult<bool>> DeleteLibrary(int libraryId)
|
|
{
|
|
var username = User.GetUsername();
|
|
_logger.LogInformation("Library {LibraryId} is being deleted by {UserName}", libraryId, username);
|
|
var series = await _unitOfWork.SeriesRepository.GetSeriesForLibraryIdAsync(libraryId);
|
|
var seriesIds = series.Select(x => x.Id).ToArray();
|
|
var chapterIds =
|
|
await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(seriesIds);
|
|
|
|
|
|
try
|
|
{
|
|
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId, LibraryIncludes.None);
|
|
_unitOfWork.LibraryRepository.Delete(library);
|
|
await _unitOfWork.CommitAsync();
|
|
|
|
if (chapterIds.Any())
|
|
{
|
|
await _unitOfWork.AppUserProgressRepository.CleanupAbandonedChapters();
|
|
await _unitOfWork.CommitAsync();
|
|
_taskScheduler.CleanupChapters(chapterIds);
|
|
}
|
|
return Ok(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "There was a critical error trying to delete the library");
|
|
await _unitOfWork.RollbackAsync();
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
[Authorize(Policy = "RequireAdminRole")]
|
|
[HttpPost("update")]
|
|
public async Task<ActionResult> UpdateLibrary(UpdateLibraryDto libraryForUserDto)
|
|
{
|
|
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryForUserDto.Id, LibraryIncludes.Folders);
|
|
|
|
var originalFolders = library.Folders.Select(x => x.Path).ToList();
|
|
|
|
library.Name = libraryForUserDto.Name;
|
|
library.Folders = libraryForUserDto.Folders.Select(s => new FolderPath() {Path = s}).ToList();
|
|
|
|
_unitOfWork.LibraryRepository.Update(library);
|
|
|
|
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was a critical issue updating the library.");
|
|
if (originalFolders.Count != libraryForUserDto.Folders.Count())
|
|
{
|
|
_taskScheduler.ScanLibrary(library.Id);
|
|
}
|
|
|
|
return Ok();
|
|
|
|
}
|
|
|
|
[HttpGet("search")]
|
|
public async Task<ActionResult<IEnumerable<SearchResultDto>>> Search(string queryString)
|
|
{
|
|
queryString = queryString.Trim().Replace(@"%", "");
|
|
|
|
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
|
// Get libraries user has access to
|
|
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId)).ToList();
|
|
|
|
if (!libraries.Any()) return BadRequest("User does not have access to any libraries");
|
|
|
|
var series = await _unitOfWork.SeriesRepository.SearchSeries(libraries.Select(l => l.Id).ToArray(), queryString);
|
|
|
|
return Ok(series);
|
|
}
|
|
|
|
[HttpGet("type")]
|
|
public async Task<ActionResult<LibraryType>> GetLibraryType(int libraryId)
|
|
{
|
|
return Ok(await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(libraryId));
|
|
}
|
|
}
|
|
}
|