mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
Added a sorting mechanism to emulate how windows sorts files. Refactored cache to support chapter folders as well.
This commit is contained in:
parent
6020697d7d
commit
f737f662df
@ -48,6 +48,7 @@ namespace API.Tests
|
||||
[InlineData("Dance in the Vampire Bund v16-17 (Digital) (NiceDragon)", "")]
|
||||
[InlineData("c001", "1")]
|
||||
[InlineData("[Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.12.zip", "12")]
|
||||
[InlineData("Adding volume 1 with File: Ana Satsujin Vol. 1 Ch. 5 - Manga Box (gb).cbz", "5")]
|
||||
public void ParseChaptersTest(string filename, string expected)
|
||||
{
|
||||
var result = ParseChapter(filename);
|
||||
|
28
API.Tests/Services/StringLogicalComparerTest.cs
Normal file
28
API.Tests/Services/StringLogicalComparerTest.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using API.Comparators;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Services
|
||||
{
|
||||
public class StringLogicalComparerTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(
|
||||
new[] {"x1.jpg", "x10.jpg", "x3.jpg", "x4.jpg", "x11.jpg"},
|
||||
new[] {"x1.jpg", "x3.jpg", "x4.jpg", "x10.jpg", "x11.jpg"}
|
||||
)]
|
||||
|
||||
public void TestLogicalComparer(string[] input, string[] expected)
|
||||
{
|
||||
NumericComparer nc = new NumericComparer();
|
||||
Array.Sort(input, nc);
|
||||
|
||||
var i = 0;
|
||||
foreach (var s in input)
|
||||
{
|
||||
Assert.Equal(s, expected[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
API/Comparators/NumericComparer.cs
Normal file
17
API/Comparators/NumericComparer.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace API.Comparators
|
||||
{
|
||||
public class NumericComparer : IComparer
|
||||
{
|
||||
|
||||
public int Compare(object x, object y)
|
||||
{
|
||||
if((x is string xs) && (y is string ys))
|
||||
{
|
||||
return StringLogicalComparer.Compare(xs, ys);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
130
API/Comparators/StringLogicalComparer.cs
Normal file
130
API/Comparators/StringLogicalComparer.cs
Normal file
@ -0,0 +1,130 @@
|
||||
//(c) Vasian Cepa 2005
|
||||
// Version 2
|
||||
// Taken from: https://www.codeproject.com/Articles/11016/Numeric-String-Sort-in-C
|
||||
|
||||
using System;
|
||||
|
||||
namespace API.Comparators
|
||||
{
|
||||
public static class StringLogicalComparer
|
||||
{
|
||||
public static int Compare(string s1, string s2)
|
||||
{
|
||||
//get rid of special cases
|
||||
if((s1 == null) && (s2 == null)) return 0;
|
||||
if(s1 == null) return -1;
|
||||
if(s2 == null) return 1;
|
||||
|
||||
if (string.IsNullOrEmpty(s1) && string.IsNullOrEmpty(s2)) return 0;
|
||||
if (string.IsNullOrEmpty(s1)) return -1;
|
||||
if (string.IsNullOrEmpty(s2)) return -1;
|
||||
|
||||
//WE style, special case
|
||||
bool sp1 = Char.IsLetterOrDigit(s1, 0);
|
||||
bool sp2 = Char.IsLetterOrDigit(s2, 0);
|
||||
if(sp1 && !sp2) return 1;
|
||||
if(!sp1 && sp2) return -1;
|
||||
|
||||
int i1 = 0, i2 = 0; //current index
|
||||
while(true)
|
||||
{
|
||||
bool c1 = Char.IsDigit(s1, i1);
|
||||
bool c2 = Char.IsDigit(s2, i2);
|
||||
var r = 0; // temp result
|
||||
if(!c1 && !c2)
|
||||
{
|
||||
bool letter1 = Char.IsLetter(s1, i1);
|
||||
bool letter2 = Char.IsLetter(s2, i2);
|
||||
if((letter1 && letter2) || (!letter1 && !letter2))
|
||||
{
|
||||
if(letter1 && letter2)
|
||||
{
|
||||
r = Char.ToLower(s1[i1]).CompareTo(Char.ToLower(s2[i2]));
|
||||
}
|
||||
else
|
||||
{
|
||||
r = s1[i1].CompareTo(s2[i2]);
|
||||
}
|
||||
if(r != 0) return r;
|
||||
}
|
||||
else if(!letter1 && letter2) return -1;
|
||||
else if(letter1 && !letter2) return 1;
|
||||
}
|
||||
else if(c1 && c2)
|
||||
{
|
||||
r = CompareNum(s1, ref i1, s2, ref i2);
|
||||
if(r != 0) return r;
|
||||
}
|
||||
else if(c1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if(c2)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
i1++;
|
||||
i2++;
|
||||
if((i1 >= s1.Length) && (i2 >= s2.Length))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if(i1 >= s1.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if(i2 >= s2.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int CompareNum(string s1, ref int i1, string s2, ref int i2)
|
||||
{
|
||||
int nzStart1 = i1, nzStart2 = i2; // nz = non zero
|
||||
int end1 = i1, end2 = i2;
|
||||
|
||||
ScanNumEnd(s1, i1, ref end1, ref nzStart1);
|
||||
ScanNumEnd(s2, i2, ref end2, ref nzStart2);
|
||||
var start1 = i1; i1 = end1 - 1;
|
||||
var start2 = i2; i2 = end2 - 1;
|
||||
|
||||
var nzLength1 = end1 - nzStart1;
|
||||
var nzLength2 = end2 - nzStart2;
|
||||
|
||||
if(nzLength1 < nzLength2) return -1;
|
||||
if(nzLength1 > nzLength2) return 1;
|
||||
|
||||
for(int j1 = nzStart1,j2 = nzStart2; j1 <= i1; j1++,j2++)
|
||||
{
|
||||
var r = s1[j1].CompareTo(s2[j2]);
|
||||
if(r != 0) return r;
|
||||
}
|
||||
// the nz parts are equal
|
||||
var length1 = end1 - start1;
|
||||
var length2 = end2 - start2;
|
||||
if(length1 == length2) return 0;
|
||||
if(length1 > length2) return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
//lookahead
|
||||
private static void ScanNumEnd(string s, int start, ref int end, ref int nzStart)
|
||||
{
|
||||
nzStart = start;
|
||||
end = start;
|
||||
bool countZeros = true;
|
||||
while(Char.IsDigit(s, end))
|
||||
{
|
||||
if(countZeros && s[end].Equals('0'))
|
||||
{
|
||||
nzStart++;
|
||||
}
|
||||
else countZeros = false;
|
||||
end++;
|
||||
if(end >= s.Length) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -71,6 +71,7 @@ namespace API.Controllers
|
||||
|
||||
if (await _userRepository.SaveAllAsync())
|
||||
{
|
||||
_logger.LogInformation($"Created a new library: {library.Name}");
|
||||
var createdLibrary = await _libraryRepository.GetLibraryForNameAsync(library.Name);
|
||||
BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(createdLibrary.Id, false));
|
||||
return Ok();
|
||||
@ -121,6 +122,7 @@ namespace API.Controllers
|
||||
|
||||
if (await _userRepository.SaveAllAsync())
|
||||
{
|
||||
_logger.LogInformation($"Added: {updateLibraryForUserDto.SelectedLibraries} to {updateLibraryForUserDto.Username}");
|
||||
return Ok(user);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Comparators;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Interfaces;
|
||||
@ -12,27 +16,26 @@ namespace API.Controllers
|
||||
private readonly ISeriesRepository _seriesRepository;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly NumericComparer _numericComparer;
|
||||
|
||||
public ReaderController(ISeriesRepository seriesRepository, IDirectoryService directoryService, ICacheService cacheService)
|
||||
{
|
||||
_seriesRepository = seriesRepository;
|
||||
_directoryService = directoryService;
|
||||
_cacheService = cacheService;
|
||||
_numericComparer = new NumericComparer();
|
||||
}
|
||||
|
||||
[HttpGet("info")]
|
||||
public async Task<ActionResult<int>> GetInformation(int volumeId)
|
||||
{
|
||||
// TODO: This will be refactored out. No longer needed.
|
||||
Volume volume = await _seriesRepository.GetVolumeAsync(volumeId);
|
||||
Volume volume = await _cacheService.Ensure(volumeId);
|
||||
|
||||
// Assume we always get first Manga File
|
||||
if (volume == null || !volume.Files.Any())
|
||||
{
|
||||
// TODO: Move this into Ensure and return negative numbers for different error codes.
|
||||
return BadRequest("There are no files in the volume to read.");
|
||||
}
|
||||
|
||||
_cacheService.Ensure(volumeId);
|
||||
|
||||
return Ok(volume.Files.Select(x => x.NumberOfPages).Sum());
|
||||
|
||||
@ -42,12 +45,12 @@ namespace API.Controllers
|
||||
public async Task<ActionResult<ImageDto>> GetImage(int volumeId, int page)
|
||||
{
|
||||
// Temp let's iterate the directory each call to get next image
|
||||
_cacheService.Ensure(volumeId);
|
||||
|
||||
|
||||
|
||||
var files = _directoryService.ListFiles(_directoryService.GetExtractPath(volumeId));
|
||||
var path = files.ElementAt(page);
|
||||
var volume = await _cacheService.Ensure(volumeId);
|
||||
|
||||
var files = _directoryService.ListFiles(_cacheService.GetCachedPagePath(volume, page));
|
||||
var array = files.ToArray();
|
||||
Array.Sort(array, _numericComparer); // TODO: Find a way to apply numericComparer to IList.
|
||||
var path = array.ElementAt(page);
|
||||
var file = await _directoryService.ReadImageAsync(path);
|
||||
file.Page = page;
|
||||
|
||||
|
@ -1,21 +1,28 @@
|
||||
using API.Entities;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
|
||||
namespace API.Interfaces
|
||||
{
|
||||
public interface ICacheService
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures the cache is created for the given volume and if not, will create it.
|
||||
/// Ensures the cache is created for the given volume and if not, will create it. Should be called before any other
|
||||
/// cache operations (except cleanup).
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
void Ensure(int volumeId);
|
||||
/// <returns>Volume for the passed volumeId. Side-effect from ensuring cache.</returns>
|
||||
Task<Volume> Ensure(int volumeId);
|
||||
|
||||
bool Cleanup(Volume volume);
|
||||
|
||||
//bool CleanupAll();
|
||||
|
||||
string GetCachePath(int volumeId);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the absolute path of a cached page.
|
||||
/// </summary>
|
||||
/// <param name="volume"></param>
|
||||
/// <param name="page">Page number to look for</param>
|
||||
/// <returns></returns>
|
||||
string GetCachedPagePath(Volume volume, int page);
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ namespace API.Interfaces
|
||||
/// </summary>
|
||||
/// <param name="rootPath">Absolute path </param>
|
||||
/// <returns>List of folder names</returns>
|
||||
IEnumerable<string> ListFiles(string rootPath);
|
||||
IList<string> ListFiles(string rootPath);
|
||||
|
||||
/// <summary>
|
||||
/// Given a library id, scans folders for said library. Parses files and generates DB updates. Will overwrite
|
||||
|
@ -178,7 +178,7 @@ namespace API.Parser
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
return "0";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using API.Interfaces;
|
||||
|
||||
@ -15,31 +17,46 @@ namespace API.Services
|
||||
_seriesRepository = seriesRepository;
|
||||
}
|
||||
|
||||
public async void Ensure(int volumeId)
|
||||
public async Task<Volume> Ensure(int volumeId)
|
||||
{
|
||||
Volume volume = await _seriesRepository.GetVolumeAsync(volumeId);
|
||||
foreach (var file in volume.Files)
|
||||
{
|
||||
var extractPath = GetCachePath(volumeId);
|
||||
if (file.Chapter > 0)
|
||||
{
|
||||
extractPath = Path.Join(extractPath, file.Chapter + "");
|
||||
}
|
||||
|
||||
var extractPath = GetVolumeCachePath(volumeId, file);
|
||||
|
||||
_directoryService.ExtractArchive(file.FilePath, extractPath);
|
||||
}
|
||||
|
||||
return volume;
|
||||
}
|
||||
|
||||
|
||||
public bool Cleanup(Volume volume)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetCachePath(int volumeId)
|
||||
private string GetVolumeCachePath(int volumeId, MangaFile file)
|
||||
{
|
||||
return Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), $"../cache/{volumeId}/"));
|
||||
var extractPath = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), $"../cache/{volumeId}/"));
|
||||
if (file.Chapter > 0)
|
||||
{
|
||||
extractPath = Path.Join(extractPath, file.Chapter + "");
|
||||
}
|
||||
return extractPath;
|
||||
}
|
||||
|
||||
public string GetCachedPagePath(Volume volume, int page)
|
||||
{
|
||||
// Calculate what chapter the page belongs to
|
||||
foreach (var mangaFile in volume.Files.OrderBy(f => f.Chapter))
|
||||
{
|
||||
if (page + 1 < mangaFile.NumberOfPages)
|
||||
{
|
||||
return GetVolumeCachePath(volume.Id, mangaFile);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -67,7 +67,7 @@ namespace API.Services
|
||||
return dirs;
|
||||
}
|
||||
|
||||
public IEnumerable<string> ListFiles(string rootPath)
|
||||
public IList<string> ListFiles(string rootPath)
|
||||
{
|
||||
if (!Directory.Exists(rootPath)) return ImmutableList<string>.Empty;
|
||||
return Directory.GetFiles(rootPath);
|
||||
|
Loading…
x
Reference in New Issue
Block a user