Implemented the ability to extract an archive image to a cache directory.

This commit is contained in:
Joseph Milazzo 2021-01-08 13:28:59 -06:00
parent 14ad2a3dd5
commit 7ab7e8acc4
8 changed files with 127 additions and 8 deletions

3
.gitignore vendored
View File

@ -447,4 +447,5 @@ appsettings.json
/API/kavita.db-shm /API/kavita.db-shm
/API/kavita.db-wal /API/kavita.db-wal
/API/Hangfire.db /API/Hangfire.db
/API/Hangfire-log.db /API/Hangfire-log.db
cache/

View File

@ -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<ActionResult<int>> 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());
}
}
}

View File

@ -76,5 +76,12 @@ namespace API.Data
return await _context.Series.Where(x => x.Id == seriesId) return await _context.Series.Where(x => x.Id == seriesId)
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).SingleAsync(); .ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).SingleAsync();
} }
public async Task<Volume> GetVolumeAsync(int volumeId)
{
return await _context.Volume
.Include(vol => vol.Files)
.SingleOrDefaultAsync(vol => vol.Id == volumeId);
}
} }
} }

View File

@ -16,6 +16,8 @@ namespace API.Entities
public uint RowVersion { get; set; } public uint RowVersion { get; set; }
public ICollection<AppUserRole> UserRoles { get; set; } public ICollection<AppUserRole> UserRoles { get; set; }
//public ICollection<SeriesProgress> SeriesProgresses { get; set; }
public void OnSavingChanges() public void OnSavingChanges()
{ {

View File

@ -4,8 +4,38 @@ namespace API.Interfaces
{ {
public interface IDirectoryService public interface IDirectoryService
{ {
/// <summary>
/// Lists out top-level folders for a given directory. Filters out System and Hidden folders.
/// </summary>
/// <param name="rootPath">Absolute path of directory to scan.</param>
/// <returns>List of folder names</returns>
IEnumerable<string> ListDirectory(string rootPath); IEnumerable<string> ListDirectory(string rootPath);
/// <summary>
/// Lists out top-level files for a given directory.
/// TODO: Implement ability to provide a filter for file types
/// </summary>
/// <param name="rootPath">Absolute path </param>
/// <returns>List of folder names</returns>
IEnumerable<string> ListFiles(string rootPath);
/// <summary>
/// Given a library id, scans folders for said library. Parses files and generates DB updates. Will overwrite
/// cover images if forceUpdate is true.
/// </summary>
/// <param name="libraryId">Library to scan against</param>
/// <param name="forceUpdate">Force overwriting for cover images</param>
void ScanLibrary(int libraryId, bool forceUpdate); void ScanLibrary(int libraryId, bool forceUpdate);
/// <summary>
/// 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).
/// </summary>
/// <param name="archivePath">A valid file to an archive file.</param>
/// <param name="volumeId">Id of volume being extracted.</param>
/// <returns></returns>
string ExtractArchive(string archivePath, int volumeId);
} }
} }

View File

@ -16,6 +16,7 @@ namespace API.Interfaces
Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId); Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId);
IEnumerable<Volume> GetVolumes(int seriesId); IEnumerable<Volume> GetVolumes(int seriesId);
Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId); Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId);
Task<Volume> GetVolumeAsync(int volumeId);
} }
} }

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@ -51,11 +52,6 @@ namespace API.Services
reSearchPattern.IsMatch(Path.GetExtension(file))); reSearchPattern.IsMatch(Path.GetExtension(file)));
} }
/// <summary>
/// Lists out top-level folders for a given directory. Filters out System and Hidden folders.
/// </summary>
/// <param name="rootPath">Absolute path </param>
/// <returns>List of folder names</returns>
public IEnumerable<string> ListDirectory(string rootPath) public IEnumerable<string> ListDirectory(string rootPath)
{ {
if (!Directory.Exists(rootPath)) return ImmutableList<string>.Empty; if (!Directory.Exists(rootPath)) return ImmutableList<string>.Empty;
@ -69,6 +65,12 @@ namespace API.Services
return dirs; return dirs;
} }
public IEnumerable<string> ListFiles(string rootPath)
{
if (!Directory.Exists(rootPath)) return ImmutableList<string>.Empty;
return Directory.GetFiles(rootPath);
}
/// <summary> /// <summary>
/// Processes files found during a library scan. Generates a collection of series->volume->files for DB processing later. /// 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() new MangaFile()
{ {
FilePath = info.File FilePath = info.FullFilePath
} }
}; };
@ -189,6 +191,7 @@ namespace API.Services
public void ScanLibrary(int libraryId, bool forceUpdate) public void ScanLibrary(int libraryId, bool forceUpdate)
{ {
var sw = Stopwatch.StartNew();
var library = Task.Run(() => _libraryRepository.GetLibraryForIdAsync(libraryId)).Result; var library = Task.Run(() => _libraryRepository.GetLibraryForIdAsync(libraryId)).Result;
_scannedSeries = new ConcurrentDictionary<string, ConcurrentBag<ParserInfo>>(); _scannedSeries = new ConcurrentDictionary<string, ConcurrentBag<ParserInfo>>();
_logger.LogInformation($"Beginning scan on {library.Name}"); _logger.LogInformation($"Beginning scan on {library.Name}");
@ -239,6 +242,35 @@ namespace API.Services
} }
_scannedSeries = null; _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<string> action) private static void TraverseTreeParallelForEach(string root, Action<string> action)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB