diff --git a/.gitignore b/.gitignore index 343c37b63..ef856c786 100644 --- a/.gitignore +++ b/.gitignore @@ -447,4 +447,5 @@ appsettings.json /API/kavita.db-shm /API/kavita.db-wal /API/Hangfire.db -/API/Hangfire-log.db \ No newline at end of file +/API/Hangfire-log.db +cache/ \ No newline at end of file diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs new file mode 100644 index 000000000..3251cce69 --- /dev/null +++ b/API/Controllers/ReaderController.cs @@ -0,0 +1,46 @@ +using System; +using System.IO.Compression; +using System.Linq; +using System.Threading.Tasks; +using API.Entities; +using API.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace API.Controllers +{ + public class ReaderController : BaseApiController + { + private readonly ISeriesRepository _seriesRepository; + private readonly IDirectoryService _directoryService; + + public ReaderController(ISeriesRepository seriesRepository, IDirectoryService directoryService) + { + _seriesRepository = seriesRepository; + _directoryService = directoryService; + } + + [HttpGet("info")] + public async Task> GetInformation(int volumeId) + { + Volume volume = await _seriesRepository.GetVolumeAsync(volumeId); + + // Assume we always get first Manga File + if (volume == null || !volume.Files.Any()) + { + return BadRequest("There are no files in the volume to read."); + } + + var filepath = volume.Files.ElementAt(0).FilePath; + + var extractPath = _directoryService.ExtractArchive(filepath, volumeId); + if (string.IsNullOrEmpty(extractPath)) + { + return BadRequest("There file is no longer there or has no images. Please rescan."); + } + + return Ok(_directoryService.ListFiles(extractPath).Count()); + } + + + } +} \ No newline at end of file diff --git a/API/Data/SeriesRepository.cs b/API/Data/SeriesRepository.cs index bed3bb093..585ff0de2 100644 --- a/API/Data/SeriesRepository.cs +++ b/API/Data/SeriesRepository.cs @@ -76,5 +76,12 @@ namespace API.Data return await _context.Series.Where(x => x.Id == seriesId) .ProjectTo(_mapper.ConfigurationProvider).SingleAsync(); } + + public async Task GetVolumeAsync(int volumeId) + { + return await _context.Volume + .Include(vol => vol.Files) + .SingleOrDefaultAsync(vol => vol.Id == volumeId); + } } } \ No newline at end of file diff --git a/API/Entities/AppUser.cs b/API/Entities/AppUser.cs index 7b7dfe8f4..9e66a7d00 100644 --- a/API/Entities/AppUser.cs +++ b/API/Entities/AppUser.cs @@ -16,6 +16,8 @@ namespace API.Entities public uint RowVersion { get; set; } public ICollection UserRoles { get; set; } + + //public ICollection SeriesProgresses { get; set; } public void OnSavingChanges() { diff --git a/API/Interfaces/IDirectoryService.cs b/API/Interfaces/IDirectoryService.cs index f8e0f7100..c938e9685 100644 --- a/API/Interfaces/IDirectoryService.cs +++ b/API/Interfaces/IDirectoryService.cs @@ -4,8 +4,38 @@ namespace API.Interfaces { public interface IDirectoryService { + /// + /// Lists out top-level folders for a given directory. Filters out System and Hidden folders. + /// + /// Absolute path of directory to scan. + /// 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 + /// + /// Absolute path + /// List of folder names + IEnumerable ListFiles(string rootPath); + + /// + /// Given a library id, scans folders for said library. Parses files and generates DB updates. Will overwrite + /// cover images if forceUpdate is true. + /// + /// Library to scan against + /// Force overwriting for cover images void ScanLibrary(int libraryId, bool forceUpdate); + + /// + /// Extracts an archive to a temp cache directory. Returns path to new directory. If temp cache directory already exists, + /// will return that without performing an extraction. Returns empty string if there are any invalidations which would + /// prevent operations to perform correctly (missing archivePath file, empty archive, etc). + /// + /// A valid file to an archive file. + /// Id of volume being extracted. + /// + string ExtractArchive(string archivePath, int volumeId); + } } \ No newline at end of file diff --git a/API/Interfaces/ISeriesRepository.cs b/API/Interfaces/ISeriesRepository.cs index fe2a6b6b3..1320788ee 100644 --- a/API/Interfaces/ISeriesRepository.cs +++ b/API/Interfaces/ISeriesRepository.cs @@ -16,6 +16,7 @@ namespace API.Interfaces Task> GetVolumesDtoAsync(int seriesId); IEnumerable GetVolumes(int seriesId); Task GetSeriesDtoByIdAsync(int seriesId); - + + Task GetVolumeAsync(int volumeId); } } \ No newline at end of file diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index 3adfae100..379d07467 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Linq; using System.Text.RegularExpressions; using System.Threading; @@ -51,11 +52,6 @@ namespace API.Services reSearchPattern.IsMatch(Path.GetExtension(file))); } - /// - /// Lists out top-level folders for a given directory. Filters out System and Hidden folders. - /// - /// Absolute path - /// List of folder names public IEnumerable ListDirectory(string rootPath) { if (!Directory.Exists(rootPath)) return ImmutableList.Empty; @@ -69,6 +65,12 @@ namespace API.Services return dirs; } + public IEnumerable ListFiles(string rootPath) + { + if (!Directory.Exists(rootPath)) return ImmutableList.Empty; + return Directory.GetFiles(rootPath); + } + /// /// Processes files found during a library scan. Generates a collection of series->volume->files for DB processing later. @@ -153,7 +155,7 @@ namespace API.Services { new MangaFile() { - FilePath = info.File + FilePath = info.FullFilePath } }; @@ -189,6 +191,7 @@ namespace API.Services public void ScanLibrary(int libraryId, bool forceUpdate) { + var sw = Stopwatch.StartNew(); var library = Task.Run(() => _libraryRepository.GetLibraryForIdAsync(libraryId)).Result; _scannedSeries = new ConcurrentDictionary>(); _logger.LogInformation($"Beginning scan on {library.Name}"); @@ -239,6 +242,35 @@ namespace API.Services } _scannedSeries = null; + Console.WriteLine("Processed {0} files in {1} milliseconds", library.Name, sw.ElapsedMilliseconds); + } + + + + public 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 = Path.Join(Directory.GetCurrentDirectory(), $"../cache/{volumeId}/"); + + if (Directory.Exists(extractPath)) + { + _logger.LogInformation($"Archive {archivePath} has already been extracted. Returning existing folder."); + return extractPath; + } + + using ZipArchive archive = ZipFile.OpenRead(archivePath); + + if (archive.Entries.Count <= 0) return ""; + + archive.ExtractToDirectory(extractPath); + _logger.LogInformation($"Extracting archive to {extractPath}"); + + return extractPath; } private static void TraverseTreeParallelForEach(string root, Action action) diff --git a/images/Image-1.jpg b/images/Image-1.jpg deleted file mode 100644 index b4dfd57c6..000000000 Binary files a/images/Image-1.jpg and /dev/null differ