mirror of
				https://github.com/Kareadita/Kavita.git
				synced 2025-11-04 03:27:05 -05:00 
			
		
		
		
	* Added book filetype detection and reorganized tests due to size of file * Added ability to get basic Parse Info from Book and Pages. * We can now scan books and get them in a library with cover images. * Take the first image in the epub if the cover isn't set. * Implemented the ability to unzip the ebup to cache. Implemented a test api to load html files. * Just some test code to figure out how to approach this. * Fixed some merge conflicts * Removed some dead code from merge * Snapshot: I can now load everything properly into the UI by rewriting the urls before I send them back. I don't notice any lag from this method. It can be optimized further. * Implemented a way to load the content in the browser not via an iframe. * Added a note * Anchor mappings is complete. New anchors are updated so references now resolve to javascript:void() for UI to take care of internally loading and the appropriate page is mapped to it. Anchors that are external have target="_blank" added so they don't force you out of the app and styles are of course inlined. * Oops i need this * Table of contents api implemented (rough) and some small enhancements to codebase for books. * GetBookPageResources now only loads files from within the book. Nested chapter list support and images now use html parsing instead of string parsing. * Fonts now are remapped to load from endpoint. * book-resources now uses a key, ensuring the file is in proper format for lookup. Changed chapter list based on structure with one HEADER and nested chapters. * Properly handle svg resource requests and when there are part anchors that are clickable, make sure we handle them in the UI by adding a kavita-page handler. * Add Chapter group page even if one isn't set by using first page (without part) from nestedChildren. * Added extra debug code for issue #163. * Added new user preferences for books and updated the css so we scope it to our reading section. * Cleaned up style code * Implemented ability to save book preferences and some cleanup on existing apis. * Added an api for checking if a user has read something in a library type before. * Forgot to make sure the has reading progress is against a user lol. * Remove cacheservice code for books, sine we use an in-memory method * Handle svg images as well * Enhanced cover image extraction to check for a "cover" image if the cover image wasn't set in OPF before falling back to the first image. * Fixed an issue with special books not properly generating metadata due to not having filename set. * Cleanup, removed warmup task code from statup/program and changed taskscheduler to schedule tasks on startup only (or if tasks are changed from UI). * Code cleanup * Code cleanup * So much code. Lots of refactors to try to test scanner service. Moved a lot of the queries into Extensions to allow to easier test, even though it's hacky. Support @font-face src:url swaps with ' and ". Source summary information from epubs. * Well...baseURL needs to come from BE and not from UI lol. * Adjusted migrations so default values match Entity * Removed comment * I think I finally fixed #163! The issue was that when i checked if it had a parserInfo, i wasn't considering that the chapter range might have a - in it (0-6) and so when the code to check if range could parse out a number failed, it treated it like a special and checked range against info's filename. * Some bugfixes * Lots of testing, extracting code to make it easier to test. This code is buggy, but fixed a bug where 1) If we changed the normalization code, we would remove the whole db during a scan and 2) We weren't actually removing series properly. Other than that, code is being extracted to remove duplication and centralize logic. * More code cleanup and test cleanup to ensure scan loop is working as expected and matches expectaions from tests. * Cleaned up the code and made it so if I change normalization, which I do in this branch, it wont break existing DBs. * Some comic parser changes for partial chapter support. * Added some code for directory service and scanner service along with python code to generate test files (not used yet). Fixed up all the tests. * Code smells
		
			
				
	
	
		
			233 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.IO;
 | 
						|
using System.Linq;
 | 
						|
using System.Threading.Tasks;
 | 
						|
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.Complete()) 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.Complete())
 | 
						|
            {
 | 
						|
                _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 chapterIds =
 | 
						|
                await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(series.Select(x => x.Id).ToArray());
 | 
						|
            var result = await _unitOfWork.LibraryRepository.DeleteLibrary(libraryId);
 | 
						|
            
 | 
						|
            if (result && chapterIds.Any())
 | 
						|
            {
 | 
						|
                _taskScheduler.CleanupChapters(chapterIds);
 | 
						|
            }
 | 
						|
            
 | 
						|
            return Ok(result);
 | 
						|
        }
 | 
						|
 | 
						|
        [Authorize(Policy = "RequireAdminRole")]
 | 
						|
        [HttpPost("update")]
 | 
						|
        public async Task<ActionResult> UpdateLibrary(UpdateLibraryDto libraryForUserDto)
 | 
						|
        {
 | 
						|
            var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryForUserDto.Id);
 | 
						|
 | 
						|
            var originalFolders = library.Folders.Select(x => x.Path);
 | 
						|
            var differenceBetweenFolders = originalFolders.Except(libraryForUserDto.Folders);
 | 
						|
 | 
						|
            library.Name = libraryForUserDto.Name;
 | 
						|
            library.Folders = libraryForUserDto.Folders.Select(s => new FolderPath() {Path = s}).ToList();
 | 
						|
 | 
						|
            _unitOfWork.LibraryRepository.Update(library);
 | 
						|
 | 
						|
            if (!await _unitOfWork.Complete()) return BadRequest("There was a critical issue updating the library.");
 | 
						|
            if (differenceBetweenFolders.Any())
 | 
						|
            {
 | 
						|
                _taskScheduler.ScanLibrary(library.Id, true);
 | 
						|
            }
 | 
						|
                
 | 
						|
            return Ok();
 | 
						|
 | 
						|
        }
 | 
						|
        
 | 
						|
        [HttpGet("search")]
 | 
						|
        public async Task<ActionResult<IEnumerable<SearchResultDto>>> Search(string queryString)
 | 
						|
        {
 | 
						|
            queryString = queryString.Replace(@"%", "");
 | 
						|
            
 | 
						|
            var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
 | 
						|
            // Get libraries user has access to
 | 
						|
            var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(user.Id)).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));
 | 
						|
        }
 | 
						|
    }
 | 
						|
} |