mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Comic Rework, New Scanner, Foundation Overahul (is this a full release?) (#2780)
This commit is contained in:
parent
d7e9e7c832
commit
7552c3f5fa
@ -6,17 +6,17 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.15" />
|
||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="20.0.15" />
|
||||
<PackageReference Include="xunit" Version="2.6.6" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.28" />
|
||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="20.0.28" />
|
||||
<PackageReference Include="xunit" Version="2.7.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -4,15 +4,16 @@ using Xunit;
|
||||
|
||||
namespace API.Tests.Comparers;
|
||||
|
||||
public class ChapterSortComparerTest
|
||||
public class ChapterSortComparerDefaultLastTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(new[] {1, 2, 0}, new[] {1, 2, 0})]
|
||||
[InlineData(new[] {1, 2, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber}, new[] {1, 2, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber})]
|
||||
[InlineData(new[] {3, 1, 2}, new[] {1, 2, 3})]
|
||||
[InlineData(new[] {1, 0, 0}, new[] {1, 0, 0})]
|
||||
[InlineData(new[] {1, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber}, new[] {1, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber})]
|
||||
[InlineData(new[] {API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber, 1}, new[] {1, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber})]
|
||||
public void ChapterSortTest(int[] input, int[] expected)
|
||||
{
|
||||
Assert.Equal(expected, input.OrderBy(f => f, new ChapterSortComparer()).ToArray());
|
||||
Assert.Equal(expected, input.OrderBy(f => f, new ChapterSortComparerDefaultLast()).ToArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ using Xunit;
|
||||
|
||||
namespace API.Tests.Comparers;
|
||||
|
||||
public class ChapterSortComparerZeroFirstTests
|
||||
public class ChapterSortComparerDefaultFirstTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(new[] {1, 2, 0}, new[] {0, 1, 2,})]
|
||||
@ -12,13 +12,13 @@ public class ChapterSortComparerZeroFirstTests
|
||||
[InlineData(new[] {1, 0, 0}, new[] {0, 0, 1})]
|
||||
public void ChapterSortComparerZeroFirstTest(int[] input, int[] expected)
|
||||
{
|
||||
Assert.Equal(expected, input.OrderBy(f => f, new ChapterSortComparerZeroFirst()).ToArray());
|
||||
Assert.Equal(expected, input.OrderBy(f => f, new ChapterSortComparerDefaultFirst()).ToArray());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new[] {1.0, 0.5, 0.3}, new[] {0.3, 0.5, 1.0})]
|
||||
public void ChapterSortComparerZeroFirstTest_Doubles(double[] input, double[] expected)
|
||||
[InlineData(new [] {1.0f, 0.5f, 0.3f}, new [] {0.3f, 0.5f, 1.0f})]
|
||||
public void ChapterSortComparerZeroFirstTest_Doubles(float[] input, float[] expected)
|
||||
{
|
||||
Assert.Equal(expected, input.OrderBy(f => f, new ChapterSortComparerZeroFirst()).ToArray());
|
||||
Assert.Equal(expected, input.OrderBy(f => f, new ChapterSortComparerDefaultFirst()).ToArray());
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,11 @@ namespace API.Tests.Comparers;
|
||||
public class SortComparerZeroLastTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(new[] {0, 1, 2,}, new[] {1, 2, 0})]
|
||||
[InlineData(new[] {API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber, 1, 2,}, new[] {1, 2, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber})]
|
||||
[InlineData(new[] {3, 1, 2}, new[] {1, 2, 3})]
|
||||
[InlineData(new[] {0, 0, 1}, new[] {1, 0, 0})]
|
||||
[InlineData(new[] {API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber, 1}, new[] {1, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber, API.Services.Tasks.Scanner.Parser.Parser.DefaultChapterNumber})]
|
||||
public void SortComparerZeroLastTest(int[] input, int[] expected)
|
||||
{
|
||||
Assert.Equal(expected, input.OrderBy(f => f, SortComparerZeroLast.Default).ToArray());
|
||||
Assert.Equal(expected, input.OrderBy(f => f, ChapterSortComparerDefaultLast.Default).ToArray());
|
||||
}
|
||||
}
|
||||
|
@ -105,6 +105,32 @@ public class ChapterListExtensionsTests
|
||||
Assert.Equal(chapterList[0], actualChapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChapterByRange_On_FilenameChange_ShouldGetChapter()
|
||||
{
|
||||
var info = new ParserInfo()
|
||||
{
|
||||
Chapters = "1",
|
||||
Edition = "",
|
||||
Format = MangaFormat.Archive,
|
||||
FullFilePath = "/manga/detective comics #001.cbz",
|
||||
Filename = "detective comics #001.cbz",
|
||||
IsSpecial = false,
|
||||
Series = "detective comics",
|
||||
Title = "detective comics",
|
||||
Volumes = API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume
|
||||
};
|
||||
|
||||
var chapterList = new List<Chapter>()
|
||||
{
|
||||
CreateChapter("1", "1", CreateFile("/manga/detective comics #001.cbz", MangaFormat.Archive), false),
|
||||
};
|
||||
|
||||
var actualChapter = chapterList.GetChapterByRange(info);
|
||||
|
||||
Assert.Equal(chapterList[0], actualChapter);
|
||||
}
|
||||
|
||||
#region GetFirstChapterWithFiles
|
||||
|
||||
[Fact]
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Linq;
|
||||
using API.Entities.Enums;
|
||||
@ -18,9 +19,8 @@ public class ParserInfoListExtensions
|
||||
private readonly IDefaultParser _defaultParser;
|
||||
public ParserInfoListExtensions()
|
||||
{
|
||||
_defaultParser =
|
||||
new DefaultParser(new DirectoryService(Substitute.For<ILogger<DirectoryService>>(),
|
||||
new MockFileSystem()));
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem());
|
||||
_defaultParser = new BasicParser(ds, new ImageParser(ds));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -33,7 +33,7 @@ public class ParserInfoListExtensions
|
||||
|
||||
[Theory]
|
||||
[InlineData(new[] {@"Cynthia The Mission - c000-006 (v06) [Desudesu&Brolen].zip"}, new[] {@"E:\Manga\Cynthia the Mission\Cynthia The Mission - c000-006 (v06) [Desudesu&Brolen].zip"}, true)]
|
||||
[InlineData(new[] {@"Cynthia The Mission - c000-006 (v06-07) [Desudesu&Brolen].zip"}, new[] {@"E:\Manga\Cynthia the Mission\Cynthia The Mission - c000-006 (v06) [Desudesu&Brolen].zip"}, true)]
|
||||
[InlineData(new[] {@"Cynthia The Mission - c000-006 (v06-07) [Desudesu&Brolen].zip"}, new[] {@"E:\Manga\Cynthia the Mission\Cynthia The Mission - c000-006 (v06) [Desudesu&Brolen].zip"}, false)]
|
||||
[InlineData(new[] {@"Cynthia The Mission v20 c12-20 [Desudesu&Brolen].zip"}, new[] {@"E:\Manga\Cynthia the Mission\Cynthia The Mission - c000-006 (v06) [Desudesu&Brolen].zip"}, false)]
|
||||
public void HasInfoTest(string[] inputInfos, string[] inputChapters, bool expectedHasInfo)
|
||||
{
|
||||
@ -41,8 +41,8 @@ public class ParserInfoListExtensions
|
||||
foreach (var filename in inputInfos)
|
||||
{
|
||||
infos.Add(_defaultParser.Parse(
|
||||
filename,
|
||||
string.Empty));
|
||||
Path.Join("E:/Manga/Cynthia the Mission/", filename),
|
||||
"E:/Manga/", "E:/Manga/", LibraryType.Manga));
|
||||
}
|
||||
|
||||
var files = inputChapters.Select(s => new MangaFileBuilder(s, MangaFormat.Archive, 199).Build()).ToList();
|
||||
@ -52,4 +52,26 @@ public class ParserInfoListExtensions
|
||||
|
||||
Assert.Equal(expectedHasInfo, infos.HasInfo(chapter));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasInfoTest_SuccessWhenSpecial()
|
||||
{
|
||||
var infos = new[]
|
||||
{
|
||||
_defaultParser.Parse(
|
||||
"E:/Manga/Cynthia the Mission/Cynthia The Mission The Special SP01 [Desudesu&Brolen].zip",
|
||||
"E:/Manga/", "E:/Manga/", LibraryType.Manga)
|
||||
};
|
||||
|
||||
var files = new[] {@"E:\Manga\Cynthia the Mission\Cynthia The Mission The Special SP01 [Desudesu&Brolen].zip"}
|
||||
.Select(s => new MangaFileBuilder(s, MangaFormat.Archive, 199).Build())
|
||||
.ToList();
|
||||
var chapter = new ChapterBuilder("Cynthia The Mission The Special SP01 [Desudesu&Brolen].zip")
|
||||
.WithRange("Cynthia The Mission The Special SP01 [Desudesu&Brolen]")
|
||||
.WithFiles(files)
|
||||
.WithIsSpecial(true)
|
||||
.Build();
|
||||
|
||||
Assert.True(infos.HasInfo(chapter));
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using API.Comparators;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Helpers.Builders;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Extensions;
|
||||
@ -17,22 +15,23 @@ public class SeriesExtensionsTests
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithName(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithVolume(new VolumeBuilder(Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithCoverImage("Special 1")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithCoverImage("Special 2")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(Parser.SpecialVolumeNumber + 2)
|
||||
.Build())
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
foreach (var vol in series.Volumes)
|
||||
{
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => x.MinNumber, ChapterSortComparerDefaultFirst.Default)?.CoverImage;
|
||||
}
|
||||
|
||||
Assert.Equal("Special 1", series.GetCoverImage());
|
||||
@ -43,8 +42,8 @@ public class SeriesExtensionsTests
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithName(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("13")
|
||||
.WithCoverImage("Chapter 13")
|
||||
.Build())
|
||||
@ -59,7 +58,7 @@ public class SeriesExtensionsTests
|
||||
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithName("Volume 2")
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithCoverImage("Volume 2")
|
||||
.Build())
|
||||
.Build())
|
||||
@ -67,12 +66,83 @@ public class SeriesExtensionsTests
|
||||
|
||||
foreach (var vol in series.Volumes)
|
||||
{
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => x.MinNumber, ChapterSortComparerDefaultFirst.Default)?.CoverImage;
|
||||
}
|
||||
|
||||
Assert.Equal("Volume 1 Chapter 1", series.GetCoverImage());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCoverImage_LooseChapters_WithSub1_Chapter()
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("-1")
|
||||
.WithCoverImage("Chapter -1")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("0.5")
|
||||
.WithCoverImage("Chapter 0.5")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("2")
|
||||
.WithCoverImage("Chapter 2")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("1")
|
||||
.WithCoverImage("Chapter 1")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("3")
|
||||
.WithCoverImage("Chapter 3")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("4AU")
|
||||
.WithCoverImage("Chapter 4AU")
|
||||
.Build())
|
||||
.Build())
|
||||
|
||||
.Build();
|
||||
|
||||
|
||||
Assert.Equal("Chapter 1", series.GetCoverImage());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the case where there are specials and loose leafs, loose leaf chapters should be preferred
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetCoverImage_LooseChapters_WithSub1_Chapter_WithSpecials()
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
|
||||
.WithVolume(new VolumeBuilder(Parser.SpecialVolume)
|
||||
.WithName(Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("I am a Special")
|
||||
.WithCoverImage("I am a Special")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("I am a Special 2")
|
||||
.WithCoverImage("I am a Special 2")
|
||||
.Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("0.5")
|
||||
.WithCoverImage("Chapter 0.5")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("2")
|
||||
.WithCoverImage("Chapter 2")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("1")
|
||||
.WithCoverImage("Chapter 1")
|
||||
.Build())
|
||||
.Build())
|
||||
|
||||
.Build();
|
||||
|
||||
|
||||
Assert.Equal("Chapter 1", series.GetCoverImage());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCoverImage_JustVolumes()
|
||||
{
|
||||
@ -81,14 +151,14 @@ public class SeriesExtensionsTests
|
||||
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithName("Volume 1")
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithCoverImage("Volume 1 Chapter 1")
|
||||
.Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithName("Volume 2")
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithCoverImage("Volume 2")
|
||||
.Build())
|
||||
.Build())
|
||||
@ -109,7 +179,7 @@ public class SeriesExtensionsTests
|
||||
|
||||
foreach (var vol in series.Volumes)
|
||||
{
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => x.MinNumber, ChapterSortComparerDefaultFirst.Default)?.CoverImage;
|
||||
}
|
||||
|
||||
Assert.Equal("Volume 1 Chapter 1", series.GetCoverImage());
|
||||
@ -120,8 +190,8 @@ public class SeriesExtensionsTests
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithName(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("2.5")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Special 1")
|
||||
@ -135,7 +205,7 @@ public class SeriesExtensionsTests
|
||||
|
||||
foreach (var vol in series.Volumes)
|
||||
{
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => x.MinNumber, ChapterSortComparerDefaultFirst.Default)?.CoverImage;
|
||||
}
|
||||
|
||||
Assert.Equal("Special 2", series.GetCoverImage());
|
||||
@ -146,8 +216,8 @@ public class SeriesExtensionsTests
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithName(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("2.5")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 2.5")
|
||||
@ -156,16 +226,19 @@ public class SeriesExtensionsTests
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 2")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithCoverImage("Special 1")
|
||||
.WithSortOrder(Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build())
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
foreach (var vol in series.Volumes)
|
||||
{
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => x.MinNumber, ChapterSortComparerDefaultFirst.Default)?.CoverImage;
|
||||
}
|
||||
|
||||
Assert.Equal("Chapter 2", series.GetCoverImage());
|
||||
@ -176,8 +249,8 @@ public class SeriesExtensionsTests
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithName(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("2.5")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 2.5")
|
||||
@ -186,14 +259,17 @@ public class SeriesExtensionsTests
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 2")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithCoverImage("Special 3")
|
||||
.WithSortOrder(Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Volume 1")
|
||||
.Build())
|
||||
@ -202,7 +278,7 @@ public class SeriesExtensionsTests
|
||||
|
||||
foreach (var vol in series.Volumes)
|
||||
{
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => x.MinNumber, ChapterSortComparerDefaultFirst.Default)?.CoverImage;
|
||||
}
|
||||
|
||||
Assert.Equal("Volume 1", series.GetCoverImage());
|
||||
@ -213,8 +289,8 @@ public class SeriesExtensionsTests
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithName(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("2.5")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 2.5")
|
||||
@ -223,14 +299,17 @@ public class SeriesExtensionsTests
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 2")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithCoverImage("Special 1")
|
||||
.WithSortOrder(Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Volume 1")
|
||||
.Build())
|
||||
@ -239,7 +318,7 @@ public class SeriesExtensionsTests
|
||||
|
||||
foreach (var vol in series.Volumes)
|
||||
{
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => x.MinNumber, ChapterSortComparerDefaultFirst.Default)?.CoverImage;
|
||||
}
|
||||
|
||||
Assert.Equal("Volume 1", series.GetCoverImage());
|
||||
@ -250,8 +329,8 @@ public class SeriesExtensionsTests
|
||||
{
|
||||
var series = new SeriesBuilder("Ippo")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithName(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("1426")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 1426")
|
||||
@ -260,21 +339,24 @@ public class SeriesExtensionsTests
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 1425")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithCoverImage("Special 1")
|
||||
.WithCoverImage("Special 3")
|
||||
.WithSortOrder(Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Volume 1")
|
||||
.Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("137")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Volume 137")
|
||||
.Build())
|
||||
@ -283,7 +365,7 @@ public class SeriesExtensionsTests
|
||||
|
||||
foreach (var vol in series.Volumes)
|
||||
{
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => x.MinNumber, ChapterSortComparerDefaultFirst.Default)?.CoverImage;
|
||||
}
|
||||
|
||||
Assert.Equal("Volume 1", series.GetCoverImage());
|
||||
@ -294,8 +376,8 @@ public class SeriesExtensionsTests
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithName(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("2.5")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 2.5")
|
||||
@ -307,7 +389,7 @@ public class SeriesExtensionsTests
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("4")
|
||||
.WithMinNumber(4)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Volume 4")
|
||||
.Build())
|
||||
@ -316,11 +398,77 @@ public class SeriesExtensionsTests
|
||||
|
||||
foreach (var vol in series.Volumes)
|
||||
{
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
vol.CoverImage = vol.Chapters.MinBy(x => x.MinNumber, ChapterSortComparerDefaultFirst.Default)?.CoverImage;
|
||||
}
|
||||
|
||||
Assert.Equal("Chapter 2", series.GetCoverImage());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that Series cover is issue 1, when there are less than 1 entities and specials
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetCoverImage_LessThanIssue1()
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("0")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 0")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("1")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 1")
|
||||
.Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(Parser.SpecialVolume)
|
||||
.WithMinNumber(4)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Volume 4")
|
||||
.Build())
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
Assert.Equal("Chapter 1", series.GetCoverImage());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that Series cover is issue 1, when there are less than 1 entities and specials
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetCoverImage_LessThanIssue1_WithNegative()
|
||||
{
|
||||
var series = new SeriesBuilder("Test 1")
|
||||
.WithFormat(MangaFormat.Archive)
|
||||
.WithVolume(new VolumeBuilder(Parser.LooseLeafVolume)
|
||||
.WithName(Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("-1")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter -1")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("0")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 0")
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("1")
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Chapter 1")
|
||||
.Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(Parser.SpecialVolume)
|
||||
.WithMinNumber(4)
|
||||
.WithChapter(new ChapterBuilder(Parser.DefaultChapter)
|
||||
.WithIsSpecial(false)
|
||||
.WithCoverImage("Volume 4")
|
||||
.Build())
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
Assert.Equal("Chapter 1", series.GetCoverImage());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -23,10 +23,41 @@ public class VolumeListExtensionsTests
|
||||
.Build(),
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithIsSpecial(true).Build())
|
||||
.Build(),
|
||||
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build(),
|
||||
};
|
||||
|
||||
var v = volumes.GetCoverImage(MangaFormat.Archive);
|
||||
Assert.Equal(volumes[0].MinNumber, volumes.GetCoverImage(MangaFormat.Archive).MinNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCoverImage_ChoosesVolume1_WhenHalf()
|
||||
{
|
||||
var volumes = new List<Volume>()
|
||||
{
|
||||
new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).Build())
|
||||
.Build(),
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("0.5").Build())
|
||||
.Build(),
|
||||
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build(),
|
||||
};
|
||||
|
||||
var v = volumes.GetCoverImage(MangaFormat.Archive);
|
||||
Assert.Equal(volumes[0].MinNumber, volumes.GetCoverImage(MangaFormat.Archive).MinNumber);
|
||||
}
|
||||
|
||||
@ -41,7 +72,12 @@ public class VolumeListExtensionsTests
|
||||
.Build(),
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithIsSpecial(true).Build())
|
||||
.Build(),
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build(),
|
||||
};
|
||||
|
||||
@ -59,7 +95,12 @@ public class VolumeListExtensionsTests
|
||||
.Build(),
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithIsSpecial(true).Build())
|
||||
.Build(),
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build(),
|
||||
};
|
||||
|
||||
@ -77,7 +118,12 @@ public class VolumeListExtensionsTests
|
||||
.Build(),
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithIsSpecial(true).Build())
|
||||
.Build(),
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build(),
|
||||
};
|
||||
|
||||
@ -95,7 +141,12 @@ public class VolumeListExtensionsTests
|
||||
.Build(),
|
||||
new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).Build())
|
||||
.Build(),
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build(),
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using API.Data;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Helpers.Builders;
|
||||
using Xunit;
|
||||
@ -12,42 +13,51 @@ public class GenreHelperTests
|
||||
[Fact]
|
||||
public void UpdateGenre_ShouldAddNewGenre()
|
||||
{
|
||||
var allGenres = new List<Genre>
|
||||
var allGenres = new Dictionary<string, Genre>
|
||||
{
|
||||
new GenreBuilder("Action").Build(),
|
||||
new GenreBuilder("action").Build(),
|
||||
new GenreBuilder("Sci-fi").Build(),
|
||||
{"Action".ToNormalized(), new GenreBuilder("Action").Build()},
|
||||
{"Sci-fi".ToNormalized(), new GenreBuilder("Sci-fi").Build()}
|
||||
};
|
||||
var genreAdded = new List<Genre>();
|
||||
var addedCount = 0;
|
||||
|
||||
GenreHelper.UpdateGenre(allGenres, new[] {"Action", "Adventure"}, genre =>
|
||||
GenreHelper.UpdateGenre(allGenres, new[] {"Action", "Adventure"}, (genre, isNew) =>
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
addedCount++;
|
||||
}
|
||||
genreAdded.Add(genre);
|
||||
});
|
||||
|
||||
Assert.Equal(2, genreAdded.Count);
|
||||
Assert.Equal(4, allGenres.Count);
|
||||
Assert.Equal(1, addedCount);
|
||||
Assert.Equal(3, allGenres.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateGenre_ShouldNotAddDuplicateGenre()
|
||||
{
|
||||
var allGenres = new List<Genre>
|
||||
var allGenres = new Dictionary<string, Genre>
|
||||
{
|
||||
new GenreBuilder("Action").Build(),
|
||||
new GenreBuilder("action").Build(),
|
||||
new GenreBuilder("Sci-fi").Build(),
|
||||
|
||||
{"Action".ToNormalized(), new GenreBuilder("Action").Build()},
|
||||
{"Sci-fi".ToNormalized(), new GenreBuilder("Sci-fi").Build()}
|
||||
};
|
||||
var genreAdded = new List<Genre>();
|
||||
var addedCount = 0;
|
||||
|
||||
GenreHelper.UpdateGenre(allGenres, new[] {"Action", "Scifi"}, genre =>
|
||||
GenreHelper.UpdateGenre(allGenres, new[] {"Action", "Scifi"}, (genre, isNew) =>
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
addedCount++;
|
||||
}
|
||||
genreAdded.Add(genre);
|
||||
});
|
||||
|
||||
Assert.Equal(3, allGenres.Count);
|
||||
Assert.Equal(0, addedCount);
|
||||
Assert.Equal(2, genreAdded.Count);
|
||||
Assert.Equal(2, allGenres.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -1,6 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using API.Data;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Helpers.Builders;
|
||||
using Xunit;
|
||||
@ -12,50 +14,50 @@ public class TagHelperTests
|
||||
[Fact]
|
||||
public void UpdateTag_ShouldAddNewTag()
|
||||
{
|
||||
var allTags = new List<Tag>
|
||||
var allTags = new Dictionary<string, Tag>
|
||||
{
|
||||
new TagBuilder("Action").Build(),
|
||||
new TagBuilder("action").Build(),
|
||||
new TagBuilder("Sci-fi").Build(),
|
||||
{"Action".ToNormalized(), new TagBuilder("Action").Build()},
|
||||
{"Sci-fi".ToNormalized(), new TagBuilder("Sci-fi").Build()}
|
||||
};
|
||||
var tagAdded = new List<Tag>();
|
||||
var tagCalled = new List<Tag>();
|
||||
var addedCount = 0;
|
||||
|
||||
TagHelper.UpdateTag(allTags, new[] {"Action", "Adventure"}, (tag, added) =>
|
||||
{
|
||||
if (added)
|
||||
{
|
||||
tagAdded.Add(tag);
|
||||
addedCount++;
|
||||
}
|
||||
|
||||
tagCalled.Add(tag);
|
||||
});
|
||||
|
||||
Assert.Single(tagAdded);
|
||||
Assert.Equal(4, allTags.Count);
|
||||
Assert.Equal(1, addedCount);
|
||||
Assert.Equal(2, tagCalled.Count());
|
||||
Assert.Equal(3, allTags.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateTag_ShouldNotAddDuplicateTag()
|
||||
{
|
||||
var allTags = new List<Tag>
|
||||
var allTags = new Dictionary<string, Tag>
|
||||
{
|
||||
new TagBuilder("Action").Build(),
|
||||
new TagBuilder("action").Build(),
|
||||
new TagBuilder("Sci-fi").Build(),
|
||||
|
||||
{"Action".ToNormalized(), new TagBuilder("Action").Build()},
|
||||
{"Sci-fi".ToNormalized(), new TagBuilder("Sci-fi").Build()}
|
||||
};
|
||||
var tagAdded = new List<Tag>();
|
||||
var tagCalled = new List<Tag>();
|
||||
var addedCount = 0;
|
||||
|
||||
TagHelper.UpdateTag(allTags, new[] {"Action", "Scifi"}, (tag, added) =>
|
||||
{
|
||||
if (added)
|
||||
{
|
||||
tagAdded.Add(tag);
|
||||
addedCount++;
|
||||
}
|
||||
TagHelper.AddTagIfNotExists(allTags, tag);
|
||||
tagCalled.Add(tag);
|
||||
});
|
||||
|
||||
Assert.Equal(3, allTags.Count);
|
||||
Assert.Empty(tagAdded);
|
||||
Assert.Equal(2, allTags.Count);
|
||||
Assert.Equal(0, addedCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
217
API.Tests/Parsers/BasicParserTests.cs
Normal file
217
API.Tests/Parsers/BasicParserTests.cs
Normal file
@ -0,0 +1,217 @@
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using API.Entities.Enums;
|
||||
using API.Services;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Parsers;
|
||||
|
||||
public class BasicParserTests
|
||||
{
|
||||
private readonly BasicParser _parser;
|
||||
private readonly ILogger<DirectoryService> _dsLogger = Substitute.For<ILogger<DirectoryService>>();
|
||||
private const string RootDirectory = "C:/Books/";
|
||||
|
||||
public BasicParserTests()
|
||||
{
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.AddDirectory("C:/Books/");
|
||||
fileSystem.AddFile("C:/Books/Harry Potter/Harry Potter - Vol 1.epub", new MockFileData(""));
|
||||
|
||||
fileSystem.AddFile("C:/Books/Accel World/Accel World - Volume 1.cbz", new MockFileData(""));
|
||||
fileSystem.AddFile("C:/Books/Accel World/Accel World - Volume 1 Chapter 2.cbz", new MockFileData(""));
|
||||
fileSystem.AddFile("C:/Books/Accel World/Accel World - Chapter 3.cbz", new MockFileData(""));
|
||||
fileSystem.AddFile("C:/Books/Accel World/Accel World Gaiden SP01.cbz", new MockFileData(""));
|
||||
|
||||
|
||||
fileSystem.AddFile("C:/Books/Accel World/cover.png", new MockFileData(""));
|
||||
|
||||
fileSystem.AddFile("C:/Books/Batman/Batman #1.cbz", new MockFileData(""));
|
||||
|
||||
var ds = new DirectoryService(_dsLogger, fileSystem);
|
||||
_parser = new BasicParser(ds, new ImageParser(ds));
|
||||
}
|
||||
|
||||
#region Parse_Books
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Parse_Manga
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when there is a loose leaf cover in the manga library, that it is ignored
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_MangaLibrary_JustCover_ShouldReturnNull()
|
||||
{
|
||||
var actual = _parser.Parse(@"C:/Books/Accel World/cover.png", "C:/Books/Accel World/",
|
||||
RootDirectory, LibraryType.Manga, null);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when there is a loose leaf cover in the manga library, that it is ignored
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_MangaLibrary_OtherImage_ShouldReturnNull()
|
||||
{
|
||||
var actual = _parser.Parse(@"C:/Books/Accel World/page 01.png", "C:/Books/Accel World/",
|
||||
RootDirectory, LibraryType.Manga, null);
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when there is a volume and chapter in filename, it appropriately parses
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_MangaLibrary_VolumeAndChapterInFilename()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Books/Mujaki no Rakuen/Mujaki no Rakuen Vol12 ch76.cbz", "C:/Books/Mujaki no Rakuen/",
|
||||
RootDirectory, LibraryType.Manga, null);
|
||||
Assert.NotNull(actual);
|
||||
|
||||
Assert.Equal("Mujaki no Rakuen", actual.Series);
|
||||
Assert.Equal("12", actual.Volumes);
|
||||
Assert.Equal("76", actual.Chapters);
|
||||
Assert.False(actual.IsSpecial);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when there is a volume in filename, it appropriately parses
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_MangaLibrary_JustVolumeInFilename()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Books/Shimoneta to Iu Gainen ga Sonzai Shinai Taikutsu na Sekai Man-hen/Vol 1.cbz",
|
||||
"C:/Books/Shimoneta to Iu Gainen ga Sonzai Shinai Taikutsu na Sekai Man-hen/",
|
||||
RootDirectory, LibraryType.Manga, null);
|
||||
Assert.NotNull(actual);
|
||||
|
||||
Assert.Equal("Shimoneta to Iu Gainen ga Sonzai Shinai Taikutsu na Sekai Man-hen", actual.Series);
|
||||
Assert.Equal("1", actual.Volumes);
|
||||
Assert.Equal(Parser.DefaultChapter, actual.Chapters);
|
||||
Assert.False(actual.IsSpecial);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when there is a chapter only in filename, it appropriately parses
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_MangaLibrary_JustChapterInFilename()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Books/Beelzebub/Beelzebub_01_[Noodles].zip",
|
||||
"C:/Books/Beelzebub/",
|
||||
RootDirectory, LibraryType.Manga, null);
|
||||
Assert.NotNull(actual);
|
||||
|
||||
Assert.Equal("Beelzebub", actual.Series);
|
||||
Assert.Equal(Parser.LooseLeafVolume, actual.Volumes);
|
||||
Assert.Equal("1", actual.Chapters);
|
||||
Assert.False(actual.IsSpecial);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when there is a SP Marker in filename, it appropriately parses
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_MangaLibrary_SpecialMarkerInFilename()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Books/Summer Time Rendering/Specials/Record 014 (between chapter 083 and ch084) SP11.cbr",
|
||||
"C:/Books/Summer Time Rendering/",
|
||||
RootDirectory, LibraryType.Manga, null);
|
||||
Assert.NotNull(actual);
|
||||
|
||||
Assert.Equal("Summer Time Rendering", actual.Series);
|
||||
Assert.Equal(Parser.SpecialVolume, actual.Volumes);
|
||||
Assert.Equal(Parser.DefaultChapter, actual.Chapters);
|
||||
Assert.True(actual.IsSpecial);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when the filename parses as a speical, it appropriately parses
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_MangaLibrary_SpecialInFilename()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Books/Summer Time Rendering/Specials/Volume Omake.cbr",
|
||||
"C:/Books/Summer Time Rendering/",
|
||||
RootDirectory, LibraryType.Manga, null);
|
||||
Assert.NotNull(actual);
|
||||
|
||||
Assert.Equal("Summer Time Rendering", actual.Series);
|
||||
Assert.Equal("Volume Omake", actual.Title);
|
||||
Assert.Equal(Parser.SpecialVolume, actual.Volumes);
|
||||
Assert.Equal(Parser.DefaultChapter, actual.Chapters);
|
||||
Assert.True(actual.IsSpecial);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when there is an edition in filename, it appropriately parses
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_MangaLibrary_EditionInFilename()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Books/Air Gear/Air Gear Omnibus v01 (2016) (Digital) (Shadowcat-Empire).cbz",
|
||||
"C:/Books/Air Gear/",
|
||||
RootDirectory, LibraryType.Manga, null);
|
||||
Assert.NotNull(actual);
|
||||
|
||||
Assert.Equal("Air Gear", actual.Series);
|
||||
Assert.Equal("1", actual.Volumes);
|
||||
Assert.Equal(Parser.DefaultChapter, actual.Chapters);
|
||||
Assert.False(actual.IsSpecial);
|
||||
Assert.Equal("Omnibus", actual.Edition);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Parse_Books
|
||||
/// <summary>
|
||||
/// Tests that when there is a volume in filename, it appropriately parses
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_MangaBooks_JustVolumeInFilename()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Books/Epubs/Harrison, Kim - The Good, The Bad, and the Undead - Hollows Vol 2.5.epub",
|
||||
"C:/Books/Epubs/",
|
||||
RootDirectory, LibraryType.Manga, null);
|
||||
Assert.NotNull(actual);
|
||||
|
||||
Assert.Equal("Harrison, Kim - The Good, The Bad, and the Undead - Hollows", actual.Series);
|
||||
Assert.Equal("2.5", actual.Volumes);
|
||||
Assert.Equal(Parser.DefaultChapter, actual.Chapters);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IsApplicable
|
||||
/// <summary>
|
||||
/// Tests that this Parser can only be used on images and Image library type
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsApplicable_Fails_WhenNonMatchingLibraryType()
|
||||
{
|
||||
Assert.False(_parser.IsApplicable("something.cbz", LibraryType.Image));
|
||||
Assert.False(_parser.IsApplicable("something.cbz", LibraryType.ComicVine));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that this Parser can only be used on images and Image library type
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsApplicable_Success_WhenMatchingLibraryType()
|
||||
{
|
||||
Assert.True(_parser.IsApplicable("something.png", LibraryType.Manga));
|
||||
Assert.True(_parser.IsApplicable("something.png", LibraryType.Comic));
|
||||
Assert.True(_parser.IsApplicable("something.pdf", LibraryType.Book));
|
||||
Assert.True(_parser.IsApplicable("something.epub", LibraryType.LightNovel));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
74
API.Tests/Parsers/BookParserTests.cs
Normal file
74
API.Tests/Parsers/BookParserTests.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using API.Data.Metadata;
|
||||
using API.Entities.Enums;
|
||||
using API.Services;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Parsers;
|
||||
|
||||
public class BookParserTests
|
||||
{
|
||||
private readonly BookParser _parser;
|
||||
private readonly ILogger<DirectoryService> _dsLogger = Substitute.For<ILogger<DirectoryService>>();
|
||||
private const string RootDirectory = "C:/Books/";
|
||||
|
||||
public BookParserTests()
|
||||
{
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.AddDirectory("C:/Books/");
|
||||
fileSystem.AddFile("C:/Books/Harry Potter/Harry Potter - Vol 1.epub", new MockFileData(""));
|
||||
fileSystem.AddFile("C:/Books/Adam Freeman - Pro ASP.NET Core 6.epub", new MockFileData(""));
|
||||
fileSystem.AddFile("C:/Books/My Fav Book SP01.epub", new MockFileData(""));
|
||||
var ds = new DirectoryService(_dsLogger, fileSystem);
|
||||
_parser = new BookParser(ds, Substitute.For<IBookService>(), new BasicParser(ds, new ImageParser(ds)));
|
||||
}
|
||||
|
||||
#region Parse
|
||||
|
||||
// TODO: I'm not sure how to actually test this as it relies on an epub parser to actually do anything
|
||||
|
||||
/// <summary>
|
||||
/// Tests that if there is a Series Folder then Chapter folder, the code appropriately identifies the Series name and Chapter
|
||||
/// </summary>
|
||||
// [Fact]
|
||||
// public void Parse_SeriesWithDirectoryName()
|
||||
// {
|
||||
// var actual = _parser.Parse("C:/Books/Harry Potter/Harry Potter - Vol 1.epub", "C:/Books/Birds of Prey/",
|
||||
// RootDirectory, LibraryType.Book, new ComicInfo()
|
||||
// {
|
||||
// Series = "Harry Potter",
|
||||
// Volume = "1"
|
||||
// });
|
||||
//
|
||||
// Assert.NotNull(actual);
|
||||
// Assert.Equal("Harry Potter", actual.Series);
|
||||
// Assert.Equal("1", actual.Volumes);
|
||||
// }
|
||||
|
||||
#endregion
|
||||
|
||||
#region IsApplicable
|
||||
/// <summary>
|
||||
/// Tests that this Parser can only be used on images and Image library type
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsApplicable_Fails_WhenNonMatchingLibraryType()
|
||||
{
|
||||
Assert.False(_parser.IsApplicable("something.cbz", LibraryType.Manga));
|
||||
Assert.False(_parser.IsApplicable("something.cbz", LibraryType.Book));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that this Parser can only be used on images and Image library type
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsApplicable_Success_WhenMatchingLibraryType()
|
||||
{
|
||||
Assert.True(_parser.IsApplicable("something.epub", LibraryType.Image));
|
||||
}
|
||||
#endregion
|
||||
}
|
115
API.Tests/Parsers/ComicVineParserTests.cs
Normal file
115
API.Tests/Parsers/ComicVineParserTests.cs
Normal file
@ -0,0 +1,115 @@
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using API.Data.Metadata;
|
||||
using API.Entities.Enums;
|
||||
using API.Services;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Parsers;
|
||||
|
||||
public class ComicVineParserTests
|
||||
{
|
||||
private readonly ComicVineParser _parser;
|
||||
private readonly ILogger<DirectoryService> _dsLogger = Substitute.For<ILogger<DirectoryService>>();
|
||||
private const string RootDirectory = "C:/Comics/";
|
||||
|
||||
public ComicVineParserTests()
|
||||
{
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.AddDirectory("C:/Comics/");
|
||||
fileSystem.AddDirectory("C:/Comics/Birds of Prey (2002)");
|
||||
fileSystem.AddFile("C:/Comics/Birds of Prey (2002)/Birds of Prey 001 (2002).cbz", new MockFileData(""));
|
||||
fileSystem.AddFile("C:/Comics/DC Comics/Birds of Prey (1999)/Birds of Prey 001 (1999).cbz", new MockFileData(""));
|
||||
fileSystem.AddFile("C:/Comics/DC Comics/Blood Syndicate/Blood Syndicate 001 (1999).cbz", new MockFileData(""));
|
||||
var ds = new DirectoryService(_dsLogger, fileSystem);
|
||||
_parser = new ComicVineParser(ds);
|
||||
}
|
||||
|
||||
#region Parse
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when Series and Volume are filled out, Kavita uses that for the Series Name
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_SeriesWithComicInfo()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/Birds of Prey (2002)/Birds of Prey 001 (2002).cbz", "C:/Comics/Birds of Prey (2002)/",
|
||||
RootDirectory, LibraryType.ComicVine, new ComicInfo()
|
||||
{
|
||||
Series = "Birds of Prey",
|
||||
Volume = "2002"
|
||||
});
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey (2002)", actual.Series);
|
||||
Assert.Equal("2002", actual.Volumes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that no ComicInfo, take the Directory Name if it matches "Series (2002)" or "Series (2)"
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_SeriesWithDirectoryNameAsSeriesYear()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/Birds of Prey (2002)/Birds of Prey 001 (2002).cbz", "C:/Comics/Birds of Prey (2002)/",
|
||||
RootDirectory, LibraryType.ComicVine, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey (2002)", actual.Series);
|
||||
Assert.Equal("2002", actual.Volumes);
|
||||
Assert.Equal("1", actual.Chapters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that no ComicInfo, take a directory name up to root if it matches "Series (2002)" or "Series (2)"
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_SeriesWithADirectoryNameAsSeriesYear()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/DC Comics/Birds of Prey (1999)/Birds of Prey 001 (1999).cbz", "C:/Comics/DC Comics/",
|
||||
RootDirectory, LibraryType.ComicVine, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey (1999)", actual.Series);
|
||||
Assert.Equal("1999", actual.Volumes);
|
||||
Assert.Equal("1", actual.Chapters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that no ComicInfo and nothing matches Series (Volume), then just take the directory name as the Series
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_FallbackToDirectoryNameOnly()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/DC Comics/Blood Syndicate/Blood Syndicate 001 (1999).cbz", "C:/Comics/DC Comics/",
|
||||
RootDirectory, LibraryType.ComicVine, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Blood Syndicate", actual.Series);
|
||||
Assert.Equal(Parser.LooseLeafVolume, actual.Volumes);
|
||||
Assert.Equal("1", actual.Chapters);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IsApplicable
|
||||
/// <summary>
|
||||
/// Tests that this Parser can only be used on ComicVine type
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsApplicable_Fails_WhenNonMatchingLibraryType()
|
||||
{
|
||||
Assert.False(_parser.IsApplicable("", LibraryType.Comic));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that this Parser can only be used on ComicVine type
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsApplicable_Success_WhenMatchingLibraryType()
|
||||
{
|
||||
Assert.True(_parser.IsApplicable("", LibraryType.ComicVine));
|
||||
}
|
||||
#endregion
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Linq;
|
||||
using API.Entities.Enums;
|
||||
using API.Services;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
@ -9,9 +7,8 @@ using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
namespace API.Tests.Parser;
|
||||
namespace API.Tests.Parsers;
|
||||
|
||||
public class DefaultParserTests
|
||||
{
|
||||
@ -22,10 +19,12 @@ public class DefaultParserTests
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
var directoryService = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem());
|
||||
_defaultParser = new DefaultParser(directoryService);
|
||||
_defaultParser = new BasicParser(directoryService, new ImageParser(directoryService));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#region ParseFromFallbackFolders
|
||||
[Theory]
|
||||
[InlineData("C:/", "C:/Love Hina/Love Hina - Special.cbz", "Love Hina")]
|
||||
@ -34,7 +33,7 @@ public class DefaultParserTests
|
||||
[InlineData("C:/", "C:/Something Random/Mujaki no Rakuen SP01.cbz", "Something Random")]
|
||||
public void ParseFromFallbackFolders_FallbackShouldParseSeries(string rootDir, string inputPath, string expectedSeries)
|
||||
{
|
||||
var actual = _defaultParser.Parse(inputPath, rootDir);
|
||||
var actual = _defaultParser.Parse(inputPath, rootDir, rootDir, LibraryType.Manga, null);
|
||||
if (actual == null)
|
||||
{
|
||||
Assert.NotNull(actual);
|
||||
@ -52,7 +51,7 @@ public class DefaultParserTests
|
||||
public void ParseFromFallbackFolders_ShouldParseSeriesVolumeAndChapter(string inputFile, string[] expectedParseInfo)
|
||||
{
|
||||
const string rootDirectory = "/manga/";
|
||||
var actual = new ParserInfo {Series = "", Chapters = API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter, Volumes = API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume};
|
||||
var actual = new ParserInfo {Series = "", Chapters = Parser.DefaultChapter, Volumes = Parser.LooseLeafVolume};
|
||||
_defaultParser.ParseFromFallbackFolders(inputFile, rootDirectory, LibraryType.Manga, ref actual);
|
||||
Assert.Equal(expectedParseInfo[0], actual.Series);
|
||||
Assert.Equal(expectedParseInfo[1], actual.Volumes);
|
||||
@ -74,8 +73,8 @@ public class DefaultParserTests
|
||||
fs.AddDirectory(rootDirectory);
|
||||
fs.AddFile(inputFile, new MockFileData(""));
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fs);
|
||||
var parser = new DefaultParser(ds);
|
||||
var actual = parser.Parse(inputFile, rootDirectory);
|
||||
var parser = new BasicParser(ds, new ImageParser(ds));
|
||||
var actual = parser.Parse(inputFile, rootDirectory, rootDirectory, LibraryType.Manga, null);
|
||||
_defaultParser.ParseFromFallbackFolders(inputFile, rootDirectory, LibraryType.Manga, ref actual);
|
||||
Assert.Equal(expectedParseInfo, actual.Series);
|
||||
}
|
||||
@ -90,8 +89,8 @@ public class DefaultParserTests
|
||||
fs.AddDirectory(rootDirectory);
|
||||
fs.AddFile(inputFile, new MockFileData(""));
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fs);
|
||||
var parser = new DefaultParser(ds);
|
||||
var actual = parser.Parse(inputFile, rootDirectory);
|
||||
var parser = new BasicParser(ds, new ImageParser(ds));
|
||||
var actual = parser.Parse(inputFile, rootDirectory, rootDirectory, LibraryType.Manga, null);
|
||||
_defaultParser.ParseFromFallbackFolders(inputFile, rootDirectory, LibraryType.Manga, ref actual);
|
||||
Assert.Equal(expectedParseInfo, actual.Series);
|
||||
}
|
||||
@ -101,13 +100,6 @@ public class DefaultParserTests
|
||||
|
||||
#region Parse
|
||||
|
||||
[Fact]
|
||||
public void Parse_MangaLibrary_JustCover_ShouldReturnNull()
|
||||
{
|
||||
const string rootPath = @"E:/Manga/";
|
||||
var actual = _defaultParser.Parse(@"E:/Manga/Accel World/cover.png", rootPath);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ParseInfo_Manga()
|
||||
@ -134,11 +126,12 @@ public class DefaultParserTests
|
||||
filepath = @"E:\Manga\Beelzebub\Beelzebub_01_[Noodles].zip";
|
||||
expected.Add(filepath, new ParserInfo
|
||||
{
|
||||
Series = "Beelzebub", Volumes = API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume,
|
||||
Series = "Beelzebub", Volumes = Parser.LooseLeafVolume,
|
||||
Chapters = "1", Filename = "Beelzebub_01_[Noodles].zip", Format = MangaFormat.Archive,
|
||||
FullFilePath = filepath
|
||||
});
|
||||
|
||||
// Note: Lots of duplicates here. I think I can move them to the ParserTests itself
|
||||
filepath = @"E:\Manga\Ichinensei ni Nacchattara\Ichinensei_ni_Nacchattara_v01_ch01_[Taruby]_v1.1.zip";
|
||||
expected.Add(filepath, new ParserInfo
|
||||
{
|
||||
@ -198,7 +191,7 @@ public class DefaultParserTests
|
||||
filepath = @"E:\Manga\Summer Time Rendering\Specials\Record 014 (between chapter 083 and ch084) SP11.cbr";
|
||||
expected.Add(filepath, new ParserInfo
|
||||
{
|
||||
Series = "Summer Time Rendering", Volumes = API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume, Edition = "",
|
||||
Series = "Summer Time Rendering", Volumes = API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume, Edition = "",
|
||||
Chapters = API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter, Filename = "Record 014 (between chapter 083 and ch084) SP11.cbr", Format = MangaFormat.Archive,
|
||||
FullFilePath = filepath, IsSpecial = true
|
||||
});
|
||||
@ -258,7 +251,7 @@ public class DefaultParserTests
|
||||
foreach (var file in expected.Keys)
|
||||
{
|
||||
var expectedInfo = expected[file];
|
||||
var actual = _defaultParser.Parse(file, rootPath);
|
||||
var actual = _defaultParser.Parse(file, rootPath, rootPath, LibraryType.Manga, null);
|
||||
if (expectedInfo == null)
|
||||
{
|
||||
Assert.Null(actual);
|
||||
@ -283,7 +276,7 @@ public class DefaultParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
//[Fact]
|
||||
public void Parse_ParseInfo_Manga_ImageOnly()
|
||||
{
|
||||
// Images don't have root path as E:\Manga, but rather as the path of the folder
|
||||
@ -296,7 +289,7 @@ public class DefaultParserTests
|
||||
Chapters = "8", Filename = "13.jpg", Format = MangaFormat.Image,
|
||||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
var actual2 = _defaultParser.Parse(filepath, @"E:\Manga\Monster #8");
|
||||
var actual2 = _defaultParser.Parse(filepath, @"E:\Manga\Monster #8", "E:/Manga", LibraryType.Manga, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
@ -322,7 +315,7 @@ public class DefaultParserTests
|
||||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
|
||||
actual2 = _defaultParser.Parse(filepath, @"E:\Manga\Extra layer for no reason\");
|
||||
actual2 = _defaultParser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga",LibraryType.Manga, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
@ -348,7 +341,7 @@ public class DefaultParserTests
|
||||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
|
||||
actual2 = _defaultParser.Parse(filepath, @"E:\Manga\Extra layer for no reason\");
|
||||
actual2 = _defaultParser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Manga, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
@ -379,7 +372,7 @@ public class DefaultParserTests
|
||||
filesystem.AddFile(@"E:/Manga/Foo 50/Specials/Foo 50 SP01.cbz", new MockFileData(""));
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var parser = new DefaultParser(ds);
|
||||
var parser = new BasicParser(ds, new ImageParser(ds));
|
||||
|
||||
var filepath = @"E:/Manga/Foo 50/Foo 50 v1.cbz";
|
||||
// There is a bad parse for series like "Foo 50", so we have parsed chapter as 50
|
||||
@ -390,7 +383,7 @@ public class DefaultParserTests
|
||||
FullFilePath = filepath
|
||||
};
|
||||
|
||||
var actual = parser.Parse(filepath, rootPath);
|
||||
var actual = parser.Parse(filepath, rootPath, rootPath, LibraryType.Manga, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
@ -414,12 +407,12 @@ public class DefaultParserTests
|
||||
filepath = @"E:/Manga/Foo 50/Specials/Foo 50 SP01.cbz";
|
||||
expected = new ParserInfo
|
||||
{
|
||||
Series = "Foo 50", Volumes = API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume, IsSpecial = true,
|
||||
Series = "Foo 50", Volumes = API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume, IsSpecial = true,
|
||||
Chapters = "50", Filename = "Foo 50 SP01.cbz", Format = MangaFormat.Archive,
|
||||
FullFilePath = filepath
|
||||
};
|
||||
|
||||
actual = parser.Parse(filepath, rootPath);
|
||||
actual = parser.Parse(filepath, rootPath, rootPath, LibraryType.Manga, null);
|
||||
Assert.NotNull(actual);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expected.Format, actual.Format);
|
||||
@ -444,12 +437,12 @@ public class DefaultParserTests
|
||||
[Fact]
|
||||
public void Parse_ParseInfo_Comic()
|
||||
{
|
||||
const string rootPath = @"E:/Comics/";
|
||||
const string rootPath = "E:/Comics/";
|
||||
var expected = new Dictionary<string, ParserInfo>();
|
||||
var filepath = @"E:/Comics/Teen Titans/Teen Titans v1 Annual 01 (1967) SP01.cbr";
|
||||
expected.Add(filepath, new ParserInfo
|
||||
{
|
||||
Series = "Teen Titans", Volumes = API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume,
|
||||
Series = "Teen Titans", Volumes = API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume,
|
||||
Chapters = API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter, Filename = "Teen Titans v1 Annual 01 (1967) SP01.cbr", Format = MangaFormat.Archive,
|
||||
FullFilePath = filepath
|
||||
});
|
||||
@ -482,7 +475,7 @@ public class DefaultParserTests
|
||||
foreach (var file in expected.Keys)
|
||||
{
|
||||
var expectedInfo = expected[file];
|
||||
var actual = _defaultParser.Parse(file, rootPath, LibraryType.Comic);
|
||||
var actual = _defaultParser.Parse(file, rootPath, rootPath, LibraryType.Comic, null);
|
||||
if (expectedInfo == null)
|
||||
{
|
||||
Assert.Null(actual);
|
97
API.Tests/Parsers/ImageParserTests.cs
Normal file
97
API.Tests/Parsers/ImageParserTests.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using API.Entities.Enums;
|
||||
using API.Services;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Parsers;
|
||||
|
||||
public class ImageParserTests
|
||||
{
|
||||
private readonly ImageParser _parser;
|
||||
private readonly ILogger<DirectoryService> _dsLogger = Substitute.For<ILogger<DirectoryService>>();
|
||||
private const string RootDirectory = "C:/Comics/";
|
||||
|
||||
public ImageParserTests()
|
||||
{
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.AddDirectory("C:/Comics/");
|
||||
fileSystem.AddDirectory("C:/Comics/Birds of Prey (2002)");
|
||||
fileSystem.AddFile("C:/Comics/Birds of Prey/Chapter 01/01.jpg", new MockFileData(""));
|
||||
fileSystem.AddFile("C:/Comics/DC Comics/Birds of Prey/Chapter 01/01.jpg", new MockFileData(""));
|
||||
var ds = new DirectoryService(_dsLogger, fileSystem);
|
||||
_parser = new ImageParser(ds);
|
||||
}
|
||||
|
||||
#region Parse
|
||||
|
||||
/// <summary>
|
||||
/// Tests that if there is a Series Folder then Chapter folder, the code appropriately identifies the Series name and Chapter
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_SeriesWithDirectoryName()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/Birds of Prey/Chapter 01/01.jpg", "C:/Comics/Birds of Prey/",
|
||||
RootDirectory, LibraryType.Image, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey", actual.Series);
|
||||
Assert.Equal("1", actual.Chapters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that if there is a Series Folder only, the code appropriately identifies the Series name from folder
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_SeriesWithNoNestedChapter()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/Birds of Prey/Chapter 01 page 01.jpg", "C:/Comics/",
|
||||
RootDirectory, LibraryType.Image, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey", actual.Series);
|
||||
Assert.Equal(Parser.DefaultChapter, actual.Chapters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that if there is a Series Folder only, the code appropriately identifies the Series name from folder and everything else as a
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_SeriesWithLooseImages()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/Birds of Prey/page 01.jpg", "C:/Comics/",
|
||||
RootDirectory, LibraryType.Image, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey", actual.Series);
|
||||
Assert.Equal(Parser.DefaultChapter, actual.Chapters);
|
||||
Assert.True(actual.IsSpecial);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region IsApplicable
|
||||
/// <summary>
|
||||
/// Tests that this Parser can only be used on images and Image library type
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsApplicable_Fails_WhenNonMatchingLibraryType()
|
||||
{
|
||||
Assert.False(_parser.IsApplicable("something.cbz", LibraryType.Manga));
|
||||
Assert.False(_parser.IsApplicable("something.cbz", LibraryType.Image));
|
||||
Assert.False(_parser.IsApplicable("something.epub", LibraryType.Image));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that this Parser can only be used on images and Image library type
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsApplicable_Success_WhenMatchingLibraryType()
|
||||
{
|
||||
Assert.True(_parser.IsApplicable("something.png", LibraryType.Image));
|
||||
}
|
||||
#endregion
|
||||
}
|
71
API.Tests/Parsers/PdfParserTests.cs
Normal file
71
API.Tests/Parsers/PdfParserTests.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using API.Entities.Enums;
|
||||
using API.Services;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Parsers;
|
||||
|
||||
public class PdfParserTests
|
||||
{
|
||||
private readonly PdfParser _parser;
|
||||
private readonly ILogger<DirectoryService> _dsLogger = Substitute.For<ILogger<DirectoryService>>();
|
||||
private const string RootDirectory = "C:/Books/";
|
||||
|
||||
public PdfParserTests()
|
||||
{
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.AddDirectory("C:/Books/");
|
||||
fileSystem.AddDirectory("C:/Books/Birds of Prey (2002)");
|
||||
fileSystem.AddFile("C:/Books/A Dictionary of Japanese Food - Ingredients and Culture/A Dictionary of Japanese Food - Ingredients and Culture.pdf", new MockFileData(""));
|
||||
fileSystem.AddFile("C:/Comics/DC Comics/Birds of Prey/Chapter 01/01.jpg", new MockFileData(""));
|
||||
var ds = new DirectoryService(_dsLogger, fileSystem);
|
||||
_parser = new PdfParser(ds);
|
||||
}
|
||||
|
||||
#region Parse
|
||||
|
||||
/// <summary>
|
||||
/// Tests that if there is a Series Folder then Chapter folder, the code appropriately identifies the Series name and Chapter
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parse_Book_SeriesWithDirectoryName()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Books/A Dictionary of Japanese Food - Ingredients and Culture/A Dictionary of Japanese Food - Ingredients and Culture.pdf",
|
||||
"C:/Books/A Dictionary of Japanese Food - Ingredients and Culture/",
|
||||
RootDirectory, LibraryType.Book, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("A Dictionary of Japanese Food - Ingredients and Culture", actual.Series);
|
||||
Assert.Equal(Parser.DefaultChapter, actual.Chapters);
|
||||
Assert.True(actual.IsSpecial);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IsApplicable
|
||||
/// <summary>
|
||||
/// Tests that this Parser can only be used on pdfs
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsApplicable_Fails_WhenNonMatchingLibraryType()
|
||||
{
|
||||
Assert.False(_parser.IsApplicable("something.cbz", LibraryType.Manga));
|
||||
Assert.False(_parser.IsApplicable("something.cbz", LibraryType.Image));
|
||||
Assert.False(_parser.IsApplicable("something.epub", LibraryType.Image));
|
||||
Assert.False(_parser.IsApplicable("something.png", LibraryType.Book));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that this Parser can only be used on pdfs
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsApplicable_Success_WhenMatchingLibraryType()
|
||||
{
|
||||
Assert.True(_parser.IsApplicable("something.pdf", LibraryType.Book));
|
||||
Assert.True(_parser.IsApplicable("something.pdf", LibraryType.Manga));
|
||||
}
|
||||
#endregion
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Parser;
|
||||
namespace API.Tests.Parsing;
|
||||
|
||||
public class BookParserTests
|
||||
public class BookParsingTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Gifting The Wonderful World With Blessings! - 3 Side Stories [yuNS][Unknown]", "Gifting The Wonderful World With Blessings!")]
|
@ -6,20 +6,18 @@ using NSubstitute;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace API.Tests.Parser;
|
||||
namespace API.Tests.Parsing;
|
||||
|
||||
public class ComicParserTests
|
||||
public class ComicParsingTests
|
||||
{
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
private readonly DefaultParser _defaultParser;
|
||||
private static readonly string DefaultVolume = API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume;
|
||||
private readonly IDefaultParser _basicParser;
|
||||
|
||||
public ComicParserTests(ITestOutputHelper testOutputHelper)
|
||||
public ComicParsingTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
_defaultParser =
|
||||
new DefaultParser(new DirectoryService(Substitute.For<ILogger<DirectoryService>>(),
|
||||
new MockFileSystem()));
|
||||
var directoryService = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem());
|
||||
_basicParser = new BasicParser(directoryService, new ImageParser(directoryService));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -210,8 +208,9 @@ public class ComicParserTests
|
||||
[InlineData("Batman Beyond Omnibus (1999)", true)]
|
||||
[InlineData("Batman Beyond Omnibus", true)]
|
||||
[InlineData("01 Annual Batman Beyond", true)]
|
||||
[InlineData("Blood Syndicate Annual #001", true)]
|
||||
public void IsComicSpecialTest(string input, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.IsComicSpecial(input));
|
||||
Assert.Equal(expected, Parser.IsComicSpecial(input));
|
||||
}
|
||||
}
|
107
API.Tests/Parsing/ImageParsingTests.cs
Normal file
107
API.Tests/Parsing/ImageParsingTests.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using API.Entities.Enums;
|
||||
using API.Services;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace API.Tests.Parsing;
|
||||
|
||||
public class ImageParsingTests
|
||||
{
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
private readonly ImageParser _parser;
|
||||
|
||||
public ImageParsingTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
var directoryService = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem());
|
||||
_parser = new ImageParser(directoryService);
|
||||
}
|
||||
|
||||
//[Fact]
|
||||
public void Parse_ParseInfo_Manga_ImageOnly()
|
||||
{
|
||||
// Images don't have root path as E:\Manga, but rather as the path of the folder
|
||||
|
||||
// Note: Fallback to folder will parse Monster #8 and get Monster
|
||||
var filepath = @"E:\Manga\Monster #8\Ch. 001-016 [MangaPlus] [Digital] [amit34521]\Monster #8 Ch. 001 [MangaPlus] [Digital] [amit34521]\13.jpg";
|
||||
var expectedInfo2 = new ParserInfo
|
||||
{
|
||||
Series = "Monster #8", Volumes = API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume, Edition = "",
|
||||
Chapters = "8", Filename = "13.jpg", Format = MangaFormat.Image,
|
||||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
var actual2 = _parser.Parse(filepath, @"E:\Manga\Monster #8", "E:/Manga", LibraryType.Image, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
_testOutputHelper.WriteLine("Format ✓");
|
||||
Assert.Equal(expectedInfo2.Series, actual2.Series);
|
||||
_testOutputHelper.WriteLine("Series ✓");
|
||||
Assert.Equal(expectedInfo2.Chapters, actual2.Chapters);
|
||||
_testOutputHelper.WriteLine("Chapters ✓");
|
||||
Assert.Equal(expectedInfo2.Volumes, actual2.Volumes);
|
||||
_testOutputHelper.WriteLine("Volumes ✓");
|
||||
Assert.Equal(expectedInfo2.Edition, actual2.Edition);
|
||||
_testOutputHelper.WriteLine("Edition ✓");
|
||||
Assert.Equal(expectedInfo2.Filename, actual2.Filename);
|
||||
_testOutputHelper.WriteLine("Filename ✓");
|
||||
Assert.Equal(expectedInfo2.FullFilePath, actual2.FullFilePath);
|
||||
_testOutputHelper.WriteLine("FullFilePath ✓");
|
||||
|
||||
filepath = @"E:\Manga\Extra layer for no reason\Just Images the second\Vol19\ch. 186\Vol. 19 p106.gif";
|
||||
expectedInfo2 = new ParserInfo
|
||||
{
|
||||
Series = "Just Images the second", Volumes = "19", Edition = "",
|
||||
Chapters = "186", Filename = "Vol. 19 p106.gif", Format = MangaFormat.Image,
|
||||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
|
||||
actual2 = _parser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Image, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
_testOutputHelper.WriteLine("Format ✓");
|
||||
Assert.Equal(expectedInfo2.Series, actual2.Series);
|
||||
_testOutputHelper.WriteLine("Series ✓");
|
||||
Assert.Equal(expectedInfo2.Chapters, actual2.Chapters);
|
||||
_testOutputHelper.WriteLine("Chapters ✓");
|
||||
Assert.Equal(expectedInfo2.Volumes, actual2.Volumes);
|
||||
_testOutputHelper.WriteLine("Volumes ✓");
|
||||
Assert.Equal(expectedInfo2.Edition, actual2.Edition);
|
||||
_testOutputHelper.WriteLine("Edition ✓");
|
||||
Assert.Equal(expectedInfo2.Filename, actual2.Filename);
|
||||
_testOutputHelper.WriteLine("Filename ✓");
|
||||
Assert.Equal(expectedInfo2.FullFilePath, actual2.FullFilePath);
|
||||
_testOutputHelper.WriteLine("FullFilePath ✓");
|
||||
|
||||
filepath = @"E:\Manga\Extra layer for no reason\Just Images the second\Blank Folder\Vol19\ch. 186\Vol. 19 p106.gif";
|
||||
expectedInfo2 = new ParserInfo
|
||||
{
|
||||
Series = "Just Images the second", Volumes = "19", Edition = "",
|
||||
Chapters = "186", Filename = "Vol. 19 p106.gif", Format = MangaFormat.Image,
|
||||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
|
||||
actual2 = _parser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Image, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
_testOutputHelper.WriteLine("Format ✓");
|
||||
Assert.Equal(expectedInfo2.Series, actual2.Series);
|
||||
_testOutputHelper.WriteLine("Series ✓");
|
||||
Assert.Equal(expectedInfo2.Chapters, actual2.Chapters);
|
||||
_testOutputHelper.WriteLine("Chapters ✓");
|
||||
Assert.Equal(expectedInfo2.Volumes, actual2.Volumes);
|
||||
_testOutputHelper.WriteLine("Volumes ✓");
|
||||
Assert.Equal(expectedInfo2.Edition, actual2.Edition);
|
||||
_testOutputHelper.WriteLine("Edition ✓");
|
||||
Assert.Equal(expectedInfo2.Filename, actual2.Filename);
|
||||
_testOutputHelper.WriteLine("Filename ✓");
|
||||
Assert.Equal(expectedInfo2.FullFilePath, actual2.FullFilePath);
|
||||
_testOutputHelper.WriteLine("FullFilePath ✓");
|
||||
}
|
||||
}
|
@ -2,13 +2,13 @@ using API.Entities.Enums;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace API.Tests.Parser;
|
||||
namespace API.Tests.Parsing;
|
||||
|
||||
public class MangaParserTests
|
||||
public class MangaParsingTests
|
||||
{
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
|
||||
public MangaParserTests(ITestOutputHelper testOutputHelper)
|
||||
public MangaParsingTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
}
|
||||
@ -294,6 +294,7 @@ public class MangaParserTests
|
||||
[InlineData("Accel World Volume 2", API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)]
|
||||
[InlineData("Historys Strongest Disciple Kenichi_v11_c90-98", "90-98")]
|
||||
[InlineData("Historys Strongest Disciple Kenichi c01-c04", "1-4")]
|
||||
[InlineData("Adabana c00-02", "0-2")]
|
||||
public void ParseChaptersTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseChapter(filename));
|
@ -2,7 +2,7 @@
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Parser;
|
||||
namespace API.Tests.Parsing;
|
||||
|
||||
public class ParserInfoTests
|
||||
{
|
@ -3,9 +3,9 @@ using System.Linq;
|
||||
using Xunit;
|
||||
using static API.Services.Tasks.Scanner.Parser.Parser;
|
||||
|
||||
namespace API.Tests.Parser;
|
||||
namespace API.Tests.Parsing;
|
||||
|
||||
public class ParserTests
|
||||
public class ParsingTests
|
||||
{
|
||||
[Fact]
|
||||
public void ShouldWork()
|
||||
@ -45,6 +45,18 @@ public class ParserTests
|
||||
Assert.Equal(expected, HasSpecialMarker(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Beastars - SP01", 1)]
|
||||
[InlineData("Beastars SP01", 1)]
|
||||
[InlineData("Beastars Special 01", 0)]
|
||||
[InlineData("Beastars Extra 01", 0)]
|
||||
[InlineData("Batman Beyond - Return of the Joker (2001) SP01", 1)]
|
||||
[InlineData("Batman Beyond - Return of the Joker (2001)", 0)]
|
||||
public void ParseSpecialIndexTest(string input, int expected)
|
||||
{
|
||||
Assert.Equal(expected, ParseSpecialIndex(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0001", "1")]
|
||||
[InlineData("1", "1")]
|
||||
@ -155,6 +167,7 @@ public class ParserTests
|
||||
[InlineData("3.5", 3.5)]
|
||||
[InlineData("3.5-4.0", 3.5)]
|
||||
[InlineData("asdfasdf", 0.0)]
|
||||
[InlineData("-10", -10.0)]
|
||||
public void MinimumNumberFromRangeTest(string input, float expected)
|
||||
{
|
||||
Assert.Equal(expected, MinNumberFromRange(input));
|
||||
@ -171,6 +184,7 @@ public class ParserTests
|
||||
[InlineData("3.5", 3.5)]
|
||||
[InlineData("3.5-4.0", 4.0)]
|
||||
[InlineData("asdfasdf", 0.0)]
|
||||
[InlineData("-10", -10.0)]
|
||||
public void MaximumNumberFromRangeTest(string input, float expected)
|
||||
{
|
||||
Assert.Equal(expected, MaxNumberFromRange(input));
|
@ -52,12 +52,12 @@ internal class MockReadingItemServiceForCacheService : IReadingItemService
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ParserInfo Parse(string path, string rootPath, LibraryType type)
|
||||
public ParserInfo Parse(string path, string rootPath, string libraryRoot, LibraryType type)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ParserInfo ParseFile(string path, string rootPath, LibraryType type)
|
||||
public ParserInfo ParseFile(string path, string rootPath, string libraryRoot, LibraryType type)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
@ -156,7 +156,9 @@ public class CacheServiceTests
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CacheService(_logger, _unitOfWork, ds,
|
||||
new ReadingItemService(Substitute.For<IArchiveService>(),
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds), Substitute.For<IBookmarkService>());
|
||||
Substitute.For<IBookService>(),
|
||||
Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
|
||||
Substitute.For<IBookmarkService>());
|
||||
|
||||
await ResetDB();
|
||||
var s = new SeriesBuilder("Test").Build();
|
||||
@ -231,7 +233,8 @@ public class CacheServiceTests
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CacheService(_logger, _unitOfWork, ds,
|
||||
new ReadingItemService(Substitute.For<IArchiveService>(),
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds), Substitute.For<IBookmarkService>());
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
|
||||
Substitute.For<IBookmarkService>());
|
||||
|
||||
cleanupService.CleanupChapters(new []{1, 3});
|
||||
Assert.Empty(ds.GetFiles(CacheDirectory, searchOption:SearchOption.AllDirectories));
|
||||
@ -252,7 +255,8 @@ public class CacheServiceTests
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cs = new CacheService(_logger, _unitOfWork, ds,
|
||||
new ReadingItemService(Substitute.For<IArchiveService>(),
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds), Substitute.For<IBookmarkService>());
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
|
||||
Substitute.For<IBookmarkService>());
|
||||
|
||||
var c = new ChapterBuilder("1")
|
||||
.WithFile(new MangaFileBuilder($"{DataDirectory}1.epub", MangaFormat.Epub).Build())
|
||||
@ -292,7 +296,8 @@ public class CacheServiceTests
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cs = new CacheService(_logger, _unitOfWork, ds,
|
||||
new ReadingItemService(Substitute.For<IArchiveService>(),
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds), Substitute.For<IBookmarkService>());
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
|
||||
Substitute.For<IBookmarkService>());
|
||||
|
||||
// Flatten to prepare for how GetFullPath expects
|
||||
ds.Flatten($"{CacheDirectory}1/");
|
||||
@ -335,7 +340,8 @@ public class CacheServiceTests
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cs = new CacheService(_logger, _unitOfWork, ds,
|
||||
new ReadingItemService(Substitute.For<IArchiveService>(),
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds), Substitute.For<IBookmarkService>());
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
|
||||
Substitute.For<IBookmarkService>());
|
||||
|
||||
// Flatten to prepare for how GetFullPath expects
|
||||
ds.Flatten($"{CacheDirectory}1/");
|
||||
@ -375,7 +381,8 @@ public class CacheServiceTests
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cs = new CacheService(_logger, _unitOfWork, ds,
|
||||
new ReadingItemService(Substitute.For<IArchiveService>(),
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds), Substitute.For<IBookmarkService>());
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
|
||||
Substitute.For<IBookmarkService>());
|
||||
|
||||
// Flatten to prepare for how GetFullPath expects
|
||||
ds.Flatten($"{CacheDirectory}1/");
|
||||
@ -419,7 +426,8 @@ public class CacheServiceTests
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cs = new CacheService(_logger, _unitOfWork, ds,
|
||||
new ReadingItemService(Substitute.For<IArchiveService>(),
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds), Substitute.For<IBookmarkService>());
|
||||
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
|
||||
Substitute.For<IBookmarkService>());
|
||||
|
||||
// Flatten to prepare for how GetFullPath expects
|
||||
ds.Flatten($"{CacheDirectory}1/");
|
||||
|
@ -395,7 +395,6 @@ public class CleanupServiceTests : AbstractDbTest
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithFormat(MangaFormat.Epub)
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(c)
|
||||
.Build())
|
||||
.Build();
|
||||
|
@ -721,6 +721,45 @@ public class DirectoryServiceTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region FindLowestDirectoriesFromFiles
|
||||
|
||||
[Theory]
|
||||
[InlineData(new [] {"C:/Manga/"},
|
||||
new [] {"C:/Manga/Love Hina/Vol. 01.cbz"},
|
||||
"C:/Manga/Love Hina")]
|
||||
[InlineData(new [] {"C:/Manga/"},
|
||||
new [] {"C:/Manga/Romance/Love Hina/Vol. 01.cbz"},
|
||||
"C:/Manga/Romance/Love Hina")]
|
||||
[InlineData(new [] {"C:/Manga/Dir 1/", "c://Manga/Dir 2/"},
|
||||
new [] {"C:/Manga/Dir 1/Love Hina/Vol. 01.cbz"},
|
||||
"C:/Manga/Dir 1/Love Hina")]
|
||||
[InlineData(new [] {"C:/Manga/Dir 1/", "c://Manga/"},
|
||||
new [] {"D:/Manga/Love Hina/Vol. 01.cbz", "D:/Manga/Vol. 01.cbz"},
|
||||
null)]
|
||||
[InlineData(new [] {"C:/Manga/"},
|
||||
new [] {"C:/Manga//Love Hina/Vol. 01.cbz"},
|
||||
"C:/Manga/Love Hina")]
|
||||
[InlineData(new [] {@"C:\mount\drive\Library\Test Library\Comics\"},
|
||||
new [] {@"C:\mount\drive\Library\Test Library\Comics\Bruce Lee (1994)\Bruce Lee #001 (1994).cbz"},
|
||||
@"C:/mount/drive/Library/Test Library/Comics/Bruce Lee (1994)")]
|
||||
public void FindLowestDirectoriesFromFilesTest(string[] rootDirectories, string[] files, string expectedDirectory)
|
||||
{
|
||||
var fileSystem = new MockFileSystem();
|
||||
foreach (var directory in rootDirectories)
|
||||
{
|
||||
fileSystem.AddDirectory(directory);
|
||||
}
|
||||
foreach (var f in files)
|
||||
{
|
||||
fileSystem.AddFile(f, new MockFileData(""));
|
||||
}
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||
|
||||
var actual = ds.FindLowestDirectoriesFromFiles(rootDirectories, files);
|
||||
Assert.Equal(expectedDirectory, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region GetFoldersTillRoot
|
||||
|
||||
[Theory]
|
||||
|
@ -54,99 +54,34 @@ internal class MockReadingItemService : IReadingItemService
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ParserInfo Parse(string path, string rootPath, LibraryType type)
|
||||
public ParserInfo Parse(string path, string rootPath, string libraryRoot, LibraryType type)
|
||||
{
|
||||
return _defaultParser.Parse(path, rootPath, type);
|
||||
return _defaultParser.Parse(path, rootPath, libraryRoot, type);
|
||||
}
|
||||
|
||||
public ParserInfo ParseFile(string path, string rootPath, LibraryType type)
|
||||
public ParserInfo ParseFile(string path, string rootPath, string libraryRoot, LibraryType type)
|
||||
{
|
||||
return _defaultParser.Parse(path, rootPath, type);
|
||||
return _defaultParser.Parse(path, rootPath, libraryRoot, type);
|
||||
}
|
||||
}
|
||||
|
||||
public class ParseScannedFilesTests
|
||||
public class ParseScannedFilesTests : AbstractDbTest
|
||||
{
|
||||
private readonly ILogger<ParseScannedFiles> _logger = Substitute.For<ILogger<ParseScannedFiles>>();
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
private readonly DbConnection _connection;
|
||||
private readonly DataContext _context;
|
||||
|
||||
private const string CacheDirectory = "C:/kavita/config/cache/";
|
||||
private const string CoverImageDirectory = "C:/kavita/config/covers/";
|
||||
private const string BackupDirectory = "C:/kavita/config/backups/";
|
||||
private const string DataDirectory = "C:/data/";
|
||||
|
||||
public ParseScannedFilesTests()
|
||||
{
|
||||
var contextOptions = new DbContextOptionsBuilder()
|
||||
.UseSqlite(CreateInMemoryDatabase())
|
||||
.Options;
|
||||
_connection = RelationalOptionsExtension.Extract(contextOptions).Connection;
|
||||
|
||||
_context = new DataContext(contextOptions);
|
||||
Task.Run(SeedDb).GetAwaiter().GetResult();
|
||||
|
||||
_unitOfWork = new UnitOfWork(_context, Substitute.For<IMapper>(), null);
|
||||
|
||||
// Since ProcessFile relies on _readingItemService, we can implement our own versions of _readingItemService so we have control over how the calls work
|
||||
|
||||
}
|
||||
|
||||
#region Setup
|
||||
|
||||
private static DbConnection CreateInMemoryDatabase()
|
||||
{
|
||||
var connection = new SqliteConnection("Filename=:memory:");
|
||||
|
||||
connection.Open();
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
private async Task<bool> SeedDb()
|
||||
{
|
||||
await _context.Database.MigrateAsync();
|
||||
var filesystem = CreateFileSystem();
|
||||
|
||||
await Seed.SeedSettings(_context, new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem));
|
||||
|
||||
var setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.CacheDirectory).SingleAsync();
|
||||
setting.Value = CacheDirectory;
|
||||
|
||||
setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.BackupDirectory).SingleAsync();
|
||||
setting.Value = BackupDirectory;
|
||||
|
||||
_context.ServerSetting.Update(setting);
|
||||
|
||||
_context.Library.Add(new LibraryBuilder("Manga")
|
||||
.WithFolderPath(new FolderPathBuilder(DataDirectory).Build())
|
||||
.Build());
|
||||
return await _context.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
private async Task ResetDB()
|
||||
protected override async Task ResetDb()
|
||||
{
|
||||
_context.Series.RemoveRange(_context.Series.ToList());
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static MockFileSystem CreateFileSystem()
|
||||
{
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Directory.SetCurrentDirectory("C:/kavita/");
|
||||
fileSystem.AddDirectory("C:/kavita/config/");
|
||||
fileSystem.AddDirectory(CacheDirectory);
|
||||
fileSystem.AddDirectory(CoverImageDirectory);
|
||||
fileSystem.AddDirectory(BackupDirectory);
|
||||
fileSystem.AddDirectory(DataDirectory);
|
||||
|
||||
return fileSystem;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MergeName
|
||||
|
||||
// NOTE: I don't think I can test MergeName as it relies on Tracking Files, which is more complicated than I need
|
||||
@ -219,6 +154,15 @@ public class ParseScannedFilesTests
|
||||
|
||||
#region ScanLibrariesForSeries
|
||||
|
||||
/// <summary>
|
||||
/// Test that when a folder has 2 series with a localizedSeries, they combine into one final series
|
||||
/// </summary>
|
||||
// [Fact]
|
||||
// public async Task ScanLibrariesForSeries_ShouldCombineSeries()
|
||||
// {
|
||||
// // TODO: Implement these unit tests
|
||||
// }
|
||||
|
||||
[Fact]
|
||||
public async Task ScanLibrariesForSeries_ShouldFindFiles()
|
||||
{
|
||||
@ -231,36 +175,42 @@ public class ParseScannedFilesTests
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||
var psf = new ParseScannedFiles(Substitute.For<ILogger<ParseScannedFiles>>(), ds,
|
||||
new MockReadingItemService(new DefaultParser(ds)), Substitute.For<IEventHub>());
|
||||
new MockReadingItemService(new BasicParser(ds, new ImageParser(ds))), Substitute.For<IEventHub>());
|
||||
|
||||
var parsedSeries = new Dictionary<ParsedSeries, IList<ParserInfo>>();
|
||||
|
||||
Task TrackFiles(Tuple<bool, IList<ParserInfo>> parsedInfo)
|
||||
{
|
||||
var skippedScan = parsedInfo.Item1;
|
||||
var parsedFiles = parsedInfo.Item2;
|
||||
if (parsedFiles.Count == 0) return Task.CompletedTask;
|
||||
|
||||
var foundParsedSeries = new ParsedSeries()
|
||||
{
|
||||
Name = parsedFiles.First().Series,
|
||||
NormalizedName = parsedFiles.First().Series.ToNormalized(),
|
||||
Format = parsedFiles.First().Format
|
||||
};
|
||||
|
||||
parsedSeries.Add(foundParsedSeries, parsedFiles);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
// var parsedSeries = new Dictionary<ParsedSeries, IList<ParserInfo>>();
|
||||
//
|
||||
// Task TrackFiles(Tuple<bool, IList<ParserInfo>> parsedInfo)
|
||||
// {
|
||||
// var skippedScan = parsedInfo.Item1;
|
||||
// var parsedFiles = parsedInfo.Item2;
|
||||
// if (parsedFiles.Count == 0) return Task.CompletedTask;
|
||||
//
|
||||
// var foundParsedSeries = new ParsedSeries()
|
||||
// {
|
||||
// Name = parsedFiles.First().Series,
|
||||
// NormalizedName = parsedFiles.First().Series.ToNormalized(),
|
||||
// Format = parsedFiles.First().Format
|
||||
// };
|
||||
//
|
||||
// parsedSeries.Add(foundParsedSeries, parsedFiles);
|
||||
// return Task.CompletedTask;
|
||||
// }
|
||||
|
||||
var library =
|
||||
await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1,
|
||||
LibraryIncludes.Folders | LibraryIncludes.FileTypes);
|
||||
Assert.NotNull(library);
|
||||
|
||||
library.Type = LibraryType.Manga;
|
||||
await psf.ScanLibrariesForSeries(library, new List<string>() {"C:/Data/"}, false, await _unitOfWork.SeriesRepository.GetFolderPathMap(1), TrackFiles);
|
||||
var parsedSeries = await psf.ScanLibrariesForSeries(library, new List<string>() {"C:/Data/"}, false,
|
||||
await _unitOfWork.SeriesRepository.GetFolderPathMap(1));
|
||||
|
||||
|
||||
Assert.Equal(3, parsedSeries.Values.Count);
|
||||
Assert.NotEmpty(parsedSeries.Keys.Where(p => p.Format == MangaFormat.Archive && p.Name.Equals("Accel World")));
|
||||
// Assert.Equal(3, parsedSeries.Values.Count);
|
||||
// Assert.NotEmpty(parsedSeries.Keys.Where(p => p.Format == MangaFormat.Archive && p.Name.Equals("Accel World")));
|
||||
|
||||
Assert.Equal(3, parsedSeries.Count);
|
||||
Assert.NotEmpty(parsedSeries.Select(p => p.ParsedSeries).Where(p => p.Format == MangaFormat.Archive && p.Name.Equals("Accel World")));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -289,18 +239,16 @@ public class ParseScannedFilesTests
|
||||
var fileSystem = CreateTestFilesystem();
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||
var psf = new ParseScannedFiles(Substitute.For<ILogger<ParseScannedFiles>>(), ds,
|
||||
new MockReadingItemService(new DefaultParser(ds)), Substitute.For<IEventHub>());
|
||||
new MockReadingItemService(new BasicParser(ds, new ImageParser(ds))), Substitute.For<IEventHub>());
|
||||
|
||||
var directoriesSeen = new HashSet<string>();
|
||||
var library =
|
||||
await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1,
|
||||
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1,
|
||||
LibraryIncludes.Folders | LibraryIncludes.FileTypes);
|
||||
await psf.ProcessFiles("C:/Data/", true, await _unitOfWork.SeriesRepository.GetFolderPathMap(1),
|
||||
(files, directoryPath) =>
|
||||
var scanResults = psf.ProcessFiles("C:/Data/", true, await _unitOfWork.SeriesRepository.GetFolderPathMap(1), library);
|
||||
foreach (var scanResult in scanResults)
|
||||
{
|
||||
directoriesSeen.Add(directoryPath);
|
||||
return Task.CompletedTask;
|
||||
}, library);
|
||||
directoriesSeen.Add(scanResult.Folder);
|
||||
}
|
||||
|
||||
Assert.Equal(2, directoriesSeen.Count);
|
||||
}
|
||||
@ -311,16 +259,20 @@ public class ParseScannedFilesTests
|
||||
var fileSystem = CreateTestFilesystem();
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||
var psf = new ParseScannedFiles(Substitute.For<ILogger<ParseScannedFiles>>(), ds,
|
||||
new MockReadingItemService(new DefaultParser(ds)), Substitute.For<IEventHub>());
|
||||
new MockReadingItemService(new BasicParser(ds, new ImageParser(ds))), Substitute.For<IEventHub>());
|
||||
|
||||
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1,
|
||||
LibraryIncludes.Folders | LibraryIncludes.FileTypes);
|
||||
Assert.NotNull(library);
|
||||
|
||||
var directoriesSeen = new HashSet<string>();
|
||||
await psf.ProcessFiles("C:/Data/", false, await _unitOfWork.SeriesRepository.GetFolderPathMap(1),
|
||||
(files, directoryPath) =>
|
||||
var scanResults = psf.ProcessFiles("C:/Data/", false,
|
||||
await _unitOfWork.SeriesRepository.GetFolderPathMap(1), library);
|
||||
|
||||
foreach (var scanResult in scanResults)
|
||||
{
|
||||
directoriesSeen.Add(directoryPath);
|
||||
return Task.CompletedTask;
|
||||
}, await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1,
|
||||
LibraryIncludes.Folders | LibraryIncludes.FileTypes));
|
||||
directoriesSeen.Add(scanResult.Folder);
|
||||
}
|
||||
|
||||
Assert.Single(directoriesSeen);
|
||||
directoriesSeen.TryGetValue("C:/Data/", out var actual);
|
||||
@ -342,18 +294,14 @@ public class ParseScannedFilesTests
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||
var psf = new ParseScannedFiles(Substitute.For<ILogger<ParseScannedFiles>>(), ds,
|
||||
new MockReadingItemService(new DefaultParser(ds)), Substitute.For<IEventHub>());
|
||||
new MockReadingItemService(new BasicParser(ds, new ImageParser(ds))), Substitute.For<IEventHub>());
|
||||
|
||||
var callCount = 0;
|
||||
await psf.ProcessFiles("C:/Data", true, await _unitOfWork.SeriesRepository.GetFolderPathMap(1),(files, folderPath) =>
|
||||
{
|
||||
callCount++;
|
||||
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1,
|
||||
LibraryIncludes.Folders | LibraryIncludes.FileTypes);
|
||||
Assert.NotNull(library);
|
||||
var scanResults = psf.ProcessFiles("C:/Data", true, await _unitOfWork.SeriesRepository.GetFolderPathMap(1), library);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}, await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1,
|
||||
LibraryIncludes.Folders | LibraryIncludes.FileTypes));
|
||||
|
||||
Assert.Equal(2, callCount);
|
||||
Assert.Equal(2, scanResults.Count);
|
||||
}
|
||||
|
||||
|
||||
@ -375,18 +323,19 @@ public class ParseScannedFilesTests
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||
var psf = new ParseScannedFiles(Substitute.For<ILogger<ParseScannedFiles>>(), ds,
|
||||
new MockReadingItemService(new DefaultParser(ds)), Substitute.For<IEventHub>());
|
||||
new MockReadingItemService(new BasicParser(ds, new ImageParser(ds))), Substitute.For<IEventHub>());
|
||||
|
||||
var callCount = 0;
|
||||
await psf.ProcessFiles("C:/Data", false, await _unitOfWork.SeriesRepository.GetFolderPathMap(1),(files, folderPath) =>
|
||||
{
|
||||
callCount++;
|
||||
return Task.CompletedTask;
|
||||
}, await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1,
|
||||
LibraryIncludes.Folders | LibraryIncludes.FileTypes));
|
||||
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1,
|
||||
LibraryIncludes.Folders | LibraryIncludes.FileTypes);
|
||||
Assert.NotNull(library);
|
||||
var scanResults = psf.ProcessFiles("C:/Data", false,
|
||||
await _unitOfWork.SeriesRepository.GetFolderPathMap(1), library);
|
||||
|
||||
Assert.Equal(1, callCount);
|
||||
Assert.Single(scanResults);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -136,7 +136,6 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithPages(1)
|
||||
.Build())
|
||||
@ -166,7 +165,6 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithPages(1)
|
||||
.Build())
|
||||
@ -205,7 +203,6 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithPages(1)
|
||||
.Build())
|
||||
@ -260,7 +257,6 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithPages(1)
|
||||
.Build())
|
||||
@ -299,7 +295,6 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithPages(1)
|
||||
.Build())
|
||||
@ -347,19 +342,16 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithMinNumber(2)
|
||||
.WithChapter(new ChapterBuilder("21").Build())
|
||||
.WithChapter(new ChapterBuilder("22").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("3")
|
||||
.WithMinNumber(3)
|
||||
.WithChapter(new ChapterBuilder("31").Build())
|
||||
.WithChapter(new ChapterBuilder("32").Build())
|
||||
.Build())
|
||||
@ -379,6 +371,7 @@ public class ReaderServiceTests
|
||||
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 1, 1);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("2", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -390,12 +383,10 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1-2")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("3-4")
|
||||
.WithMinNumber(2)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.Build())
|
||||
.Build();
|
||||
@ -412,6 +403,7 @@ public class ReaderServiceTests
|
||||
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 1, 1);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("3-4", actualChapter.Volume.Name);
|
||||
Assert.Equal("1", actualChapter.Range);
|
||||
}
|
||||
@ -456,6 +448,7 @@ public class ReaderServiceTests
|
||||
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 2, 1);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("31", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -466,19 +459,16 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithMinNumber(2)
|
||||
.WithChapter(new ChapterBuilder("21").Build())
|
||||
.WithChapter(new ChapterBuilder("22").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("3")
|
||||
.WithMinNumber(3)
|
||||
.WithChapter(new ChapterBuilder("31").Build())
|
||||
.WithChapter(new ChapterBuilder("32").Build())
|
||||
.Build())
|
||||
@ -497,6 +487,7 @@ public class ReaderServiceTests
|
||||
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("21", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -507,19 +498,16 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1.5")
|
||||
.WithMinNumber(2)
|
||||
.WithChapter(new ChapterBuilder("21").Build())
|
||||
.WithChapter(new ChapterBuilder("22").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("3")
|
||||
.WithMinNumber(3)
|
||||
.WithChapter(new ChapterBuilder("31").Build())
|
||||
.WithChapter(new ChapterBuilder("32").Build())
|
||||
.Build())
|
||||
@ -539,6 +527,7 @@ public class ReaderServiceTests
|
||||
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("21", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -549,15 +538,13 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.WithChapter(new ChapterBuilder("21").Build())
|
||||
.WithChapter(new ChapterBuilder("22").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("21").Build())
|
||||
.WithChapter(new ChapterBuilder("22").Build())
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -574,7 +561,8 @@ public class ReaderServiceTests
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 4, 1);
|
||||
Assert.NotEqual(-1, nextChapter);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter);
|
||||
Assert.Equal("1", actualChapter.Range);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("21", actualChapter.Range);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -584,18 +572,15 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("66").Build())
|
||||
.WithChapter(new ChapterBuilder("67").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithMinNumber(2)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
@ -616,6 +601,7 @@ public class ReaderServiceTests
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 3, 1);
|
||||
Assert.NotEqual(-1, nextChapter);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter, actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -626,15 +612,13 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("A.cbz").WithIsSpecial(true).Build())
|
||||
.WithChapter(new ChapterBuilder("B.cbz").WithIsSpecial(true).Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("A.cbz").WithIsSpecial(true).WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber).Build())
|
||||
.WithChapter(new ChapterBuilder("B.cbz").WithIsSpecial(true).WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -658,7 +642,6 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
@ -684,7 +667,6 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
@ -704,68 +686,69 @@ public class ReaderServiceTests
|
||||
}
|
||||
|
||||
// This is commented out because, while valid, I can't solve how to make this pass (https://github.com/Kareadita/Kavita/issues/2099)
|
||||
// [Fact]
|
||||
// public async Task GetNextChapterIdAsync_ShouldFindNoNextChapterFromLastChapter_NoSpecials_FirstIsVolume()
|
||||
// {
|
||||
// await ResetDb();
|
||||
//
|
||||
// var series = new SeriesBuilder("Test")
|
||||
// .WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultVolume)
|
||||
// .WithMinNumber(0)
|
||||
// .WithChapter(new ChapterBuilder("1").Build())
|
||||
// .WithChapter(new ChapterBuilder("2").Build())
|
||||
// .Build())
|
||||
// .WithVolume(new VolumeBuilder("1")
|
||||
// .WithMinNumber(1)
|
||||
// .WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).Build())
|
||||
// .Build())
|
||||
// .Build();
|
||||
// series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
//
|
||||
// _context.Series.Add(series);
|
||||
// _context.AppUser.Add(new AppUser()
|
||||
// {
|
||||
// UserName = "majora2007"
|
||||
// });
|
||||
//
|
||||
// await _context.SaveChangesAsync();
|
||||
//
|
||||
// var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 3, 1);
|
||||
// Assert.Equal(-1, nextChapter);
|
||||
// }
|
||||
[Fact]
|
||||
public async Task GetNextChapterIdAsync_ShouldFindNoNextChapterFromLastChapter_NoSpecials_FirstIsVolume()
|
||||
{
|
||||
await ResetDb();
|
||||
|
||||
// This is commented out because, while valid, I can't solve how to make this pass
|
||||
// [Fact]
|
||||
// public async Task GetNextChapterIdAsync_ShouldFindNoNextChapterFromLastChapter_WithSpecials()
|
||||
// {
|
||||
// await ResetDb();
|
||||
//
|
||||
// var series = new SeriesBuilder("Test")
|
||||
// .WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultVolume)
|
||||
// .WithMinNumber(0)
|
||||
// .WithChapter(new ChapterBuilder("1").Build())
|
||||
// .WithChapter(new ChapterBuilder("2").Build())
|
||||
// .WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithIsSpecial(true).Build())
|
||||
// .Build())
|
||||
//
|
||||
// .WithVolume(new VolumeBuilder("1")
|
||||
// .WithMinNumber(1)
|
||||
// .WithChapter(new ChapterBuilder("2").Build())
|
||||
// .Build())
|
||||
// .Build();
|
||||
// series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
//
|
||||
// _context.Series.Add(series);
|
||||
// _context.AppUser.Add(new AppUser()
|
||||
// {
|
||||
// UserName = "majora2007"
|
||||
// });
|
||||
//
|
||||
// await _context.SaveChangesAsync();
|
||||
//
|
||||
// var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 4, 1);
|
||||
// Assert.Equal(-1, nextChapter);
|
||||
// }
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
|
||||
_context.Series.Add(series);
|
||||
_context.AppUser.Add(new AppUser()
|
||||
{
|
||||
UserName = "majora2007"
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1);
|
||||
Assert.Equal(-1, nextChapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetNextChapterIdAsync_ShouldFindNoNextChapterFromLastChapter_WithSpecials()
|
||||
{
|
||||
await ResetDb();
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
|
||||
_context.Series.Add(series);
|
||||
_context.AppUser.Add(new AppUser()
|
||||
{
|
||||
UserName = "majora2007"
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 3, 1);
|
||||
Assert.Equal(-1, nextChapter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -776,15 +759,19 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("A.cbz").WithIsSpecial(true).Build())
|
||||
.WithChapter(new ChapterBuilder("B.cbz").WithIsSpecial(true).Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("A.cbz")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("B.cbz")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 2)
|
||||
.Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -802,6 +789,7 @@ public class ReaderServiceTests
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1);
|
||||
Assert.NotEqual(-1, nextChapter);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("A.cbz", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -812,10 +800,16 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.WithChapter(new ChapterBuilder("A.cbz").WithIsSpecial(true).Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("A.cbz")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.WithPages(1)
|
||||
.Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -833,6 +827,7 @@ public class ReaderServiceTests
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1);
|
||||
Assert.NotEqual(-1, nextChapter);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("A.cbz", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -843,15 +838,21 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.WithChapter(new ChapterBuilder("A.cbz").WithIsSpecial(true).Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("A.cbz")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.WithPages(1)
|
||||
.Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
|
||||
@ -864,7 +865,7 @@ public class ReaderServiceTests
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 3, 1);
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 3, 4, 1);
|
||||
Assert.Equal(-1, nextChapter);
|
||||
}
|
||||
|
||||
@ -876,14 +877,18 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("A.cbz").WithIsSpecial(true).Build())
|
||||
.WithChapter(new ChapterBuilder("B.cbz").WithIsSpecial(true).Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("A.cbz")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("B.cbz")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 2)
|
||||
.Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -901,6 +906,7 @@ public class ReaderServiceTests
|
||||
var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 3, 1);
|
||||
Assert.NotEqual(-1, nextChapter);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("B.cbz", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -911,12 +917,10 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("12").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithMinNumber(2)
|
||||
.WithChapter(new ChapterBuilder("12").Build())
|
||||
.Build())
|
||||
.Build();
|
||||
@ -952,19 +956,16 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithMinNumber(2)
|
||||
.WithChapter(new ChapterBuilder("21").Build())
|
||||
.WithChapter(new ChapterBuilder("22").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("3")
|
||||
.WithMinNumber(3)
|
||||
.WithChapter(new ChapterBuilder("31").Build())
|
||||
.WithChapter(new ChapterBuilder("32").Build())
|
||||
.Build())
|
||||
@ -984,6 +985,7 @@ public class ReaderServiceTests
|
||||
|
||||
var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 1, 2, 1);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("1", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -995,19 +997,16 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1.5")
|
||||
.WithMinNumber(2)
|
||||
.WithChapter(new ChapterBuilder("21").Build())
|
||||
.WithChapter(new ChapterBuilder("22").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("3")
|
||||
.WithMinNumber(3)
|
||||
.WithChapter(new ChapterBuilder("31").Build())
|
||||
.WithChapter(new ChapterBuilder("32").Build())
|
||||
.Build())
|
||||
@ -1025,6 +1024,7 @@ public class ReaderServiceTests
|
||||
|
||||
var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 3, 5, 1);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("22", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -1038,7 +1038,14 @@ public class ReaderServiceTests
|
||||
.WithChapter(new ChapterBuilder("40").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("50").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("60").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithPages(1).WithIsSpecial(true).Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Some Special Title")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.WithPages(1)
|
||||
.Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1997")
|
||||
@ -1065,7 +1072,7 @@ public class ReaderServiceTests
|
||||
|
||||
|
||||
// prevChapter should be id from ch.21 from volume 2001
|
||||
var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 4, 7, 1);
|
||||
var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 5, 7, 1);
|
||||
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
@ -1109,6 +1116,7 @@ public class ReaderServiceTests
|
||||
|
||||
var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 2, 3, 1);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("2", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -1119,15 +1127,13 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("A.cbz").WithIsSpecial(true).Build())
|
||||
.WithChapter(new ChapterBuilder("B.cbz").WithIsSpecial(true).Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("A.cbz").WithIsSpecial(true).WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).Build())
|
||||
.WithChapter(new ChapterBuilder("B.cbz").WithIsSpecial(true).WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 2).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -1147,6 +1153,7 @@ public class ReaderServiceTests
|
||||
var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 2, 3, 1);
|
||||
Assert.Equal(2, prevChapter);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("2", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -1157,7 +1164,6 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
@ -1187,7 +1193,6 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
@ -1216,13 +1221,11 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
@ -1237,10 +1240,7 @@ public class ReaderServiceTests
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
|
||||
|
||||
|
||||
var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 1, 1, 1);
|
||||
var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 2, 3, 1);
|
||||
Assert.Equal(-1, prevChapter);
|
||||
}
|
||||
|
||||
@ -1251,22 +1251,19 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("5").Build())
|
||||
.WithChapter(new ChapterBuilder("6").Build())
|
||||
.WithChapter(new ChapterBuilder("7").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").WithIsSpecial(true).Build())
|
||||
.WithChapter(new ChapterBuilder("2").WithIsSpecial(true).Build())
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithMinNumber(2)
|
||||
.WithChapter(new ChapterBuilder("3").WithIsSpecial(true).Build())
|
||||
.WithChapter(new ChapterBuilder("4").WithIsSpecial(true).Build())
|
||||
.WithChapter(new ChapterBuilder("3").Build())
|
||||
.WithChapter(new ChapterBuilder("4").Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -1299,7 +1296,6 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
@ -1329,14 +1325,18 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("A.cbz").WithIsSpecial(true).Build())
|
||||
.WithChapter(new ChapterBuilder("B.cbz").WithIsSpecial(true).Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("A.cbz")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.Build())
|
||||
.WithChapter(new ChapterBuilder("B.cbz")
|
||||
.WithIsSpecial(true)
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 2)
|
||||
.Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -1357,6 +1357,7 @@ public class ReaderServiceTests
|
||||
var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 2, 4, 1);
|
||||
Assert.NotEqual(-1, prevChapter);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("A.cbz", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -1367,12 +1368,10 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithMinNumber(0)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("21").Build())
|
||||
.WithChapter(new ChapterBuilder("22").Build())
|
||||
.Build())
|
||||
@ -1389,12 +1388,10 @@ public class ReaderServiceTests
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
|
||||
|
||||
|
||||
var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 1, 1, 1);
|
||||
Assert.NotEqual(-1, prevChapter);
|
||||
var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter);
|
||||
Assert.NotNull(actualChapter);
|
||||
Assert.Equal("22", actualChapter.Range);
|
||||
}
|
||||
|
||||
@ -1405,12 +1402,10 @@ public class ReaderServiceTests
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("12").Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithMinNumber(2)
|
||||
.WithChapter(new ChapterBuilder("12").Build())
|
||||
.Build())
|
||||
.Build();
|
||||
@ -1630,7 +1625,12 @@ public class ReaderServiceTests
|
||||
.WithChapter(new ChapterBuilder("46").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("47").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("48").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Some Special Title")
|
||||
.WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1)
|
||||
.WithIsSpecial(true).WithPages(1)
|
||||
.Build())
|
||||
.Build())
|
||||
// One file volume
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
@ -1697,7 +1697,9 @@ public class ReaderServiceTests
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("2").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("Prologue").WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Prologue").WithIsSpecial(true).WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).WithPages(1).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -1821,7 +1823,9 @@ public class ReaderServiceTests
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithChapter(new ChapterBuilder("100").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("101").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("Christmas Eve").WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Christmas Eve").WithIsSpecial(true).WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).WithPages(1).Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
@ -2031,7 +2035,9 @@ public class ReaderServiceTests
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("2").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("3").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithIsSpecial(true).WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).WithPages(1).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -2211,7 +2217,9 @@ public class ReaderServiceTests
|
||||
.WithChapter(new ChapterBuilder("51").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("52").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("91").WithPages(2).Build())
|
||||
.WithChapter(new ChapterBuilder("Special").WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Special").WithIsSpecial(true).WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).WithPages(1).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -2380,7 +2388,9 @@ public class ReaderServiceTests
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("2").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("3").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
@ -2418,8 +2428,10 @@ public class ReaderServiceTests
|
||||
.WithChapter(new ChapterBuilder("2").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("2.5").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("3").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||
|
||||
@ -2493,7 +2505,9 @@ public class ReaderServiceTests
|
||||
.WithChapter(new ChapterBuilder("48").WithPages(48).Build())
|
||||
.WithChapter(new ChapterBuilder("49").WithPages(49).Build())
|
||||
.WithChapter(new ChapterBuilder("50").WithPages(50).Build())
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithIsSpecial(true).WithPages(10).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).WithIsSpecial(true).WithPages(10).Build())
|
||||
.Build())
|
||||
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
@ -2550,14 +2564,14 @@ public class ReaderServiceTests
|
||||
public async Task MarkSeriesAsReadTest()
|
||||
{
|
||||
await ResetDb();
|
||||
// TODO: Validate this is correct, shouldn't be possible to have 2 Volume 0's in a series
|
||||
|
||||
var series = new SeriesBuilder("Test")
|
||||
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(2).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume)
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(2).Build())
|
||||
.Build())
|
||||
@ -2669,7 +2683,9 @@ public class ReaderServiceTests
|
||||
.WithChapter(new ChapterBuilder("10").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("20").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("30").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1997")
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithPages(1).Build())
|
||||
@ -2722,7 +2738,9 @@ public class ReaderServiceTests
|
||||
.WithChapter(new ChapterBuilder("10").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("20").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("30").WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolume)
|
||||
.WithChapter(new ChapterBuilder("Some Special Title").WithSortOrder(API.Services.Tasks.Scanner.Parser.Parser.SpecialVolumeNumber + 1).WithIsSpecial(true).WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1997")
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(1).Build())
|
||||
|
@ -1205,6 +1205,65 @@ public class ReadingListServiceTests
|
||||
Assert.Equal(2, createdList.Items.First(item => item.Order == 2).ChapterId);
|
||||
Assert.Equal(4, createdList.Items.First(item => item.Order == 3).ChapterId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This test is about ensuring Annuals that are a separate series can be linked up properly (ComicVine)
|
||||
/// </summary>
|
||||
//[Fact]
|
||||
public async Task CreateReadingListFromCBL_ShouldCreateList_WithAnnuals()
|
||||
{
|
||||
// TODO: Implement this correctly
|
||||
await ResetDb();
|
||||
var cblReadingList = LoadCblFromPath("Annual.cbl");
|
||||
|
||||
// Mock up our series
|
||||
var fablesSeries = new SeriesBuilder("Fables")
|
||||
.WithVolume(new VolumeBuilder("2002")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.WithChapter(new ChapterBuilder("2").Build())
|
||||
.WithChapter(new ChapterBuilder("3").Build())
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
var fables2Series = new SeriesBuilder("Fables Annual")
|
||||
.WithVolume(new VolumeBuilder("2003")
|
||||
.WithMinNumber(1)
|
||||
.WithChapter(new ChapterBuilder("1").Build())
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
_context.AppUser.Add(new AppUser()
|
||||
{
|
||||
UserName = "majora2007",
|
||||
ReadingLists = new List<ReadingList>(),
|
||||
Libraries = new List<Library>()
|
||||
{
|
||||
new LibraryBuilder("Test LIb 2", LibraryType.Book)
|
||||
.WithSeries(fablesSeries)
|
||||
.WithSeries(fables2Series)
|
||||
.Build()
|
||||
},
|
||||
});
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
var importSummary = await _readingListService.CreateReadingListFromCbl(1, cblReadingList);
|
||||
|
||||
Assert.Equal(CblImportResult.Success, importSummary.Success);
|
||||
Assert.NotEmpty(importSummary.Results);
|
||||
|
||||
var createdList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(1);
|
||||
|
||||
Assert.NotNull(createdList);
|
||||
Assert.Equal("Annual", createdList.Title);
|
||||
|
||||
Assert.Equal(4, createdList.Items.Count);
|
||||
Assert.Equal(1, createdList.Items.First(item => item.Order == 0).ChapterId);
|
||||
Assert.Equal(2, createdList.Items.First(item => item.Order == 1).ChapterId);
|
||||
Assert.Equal(4, createdList.Items.First(item => item.Order == 2).ChapterId);
|
||||
Assert.Equal(3, createdList.Items.First(item => item.Order == 3).ChapterId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CreateReadingListsFromSeries
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -130,7 +130,7 @@ public class TachiyomiServiceTests
|
||||
.WithChapter(new ChapterBuilder("96").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder("1").WithIsSpecial(true).WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithChapter(new ChapterBuilder("3").WithPages(1).Build())
|
||||
@ -175,7 +175,7 @@ public class TachiyomiServiceTests
|
||||
.WithChapter(new ChapterBuilder("96").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder("1").WithIsSpecial(true).WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithChapter(new ChapterBuilder("3").WithPages(1).Build())
|
||||
@ -265,6 +265,7 @@ public class TachiyomiServiceTests
|
||||
|
||||
Assert.Equal("21", latestChapter.Number);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetLatestChapter_ShouldReturnEncodedVolume_Progress()
|
||||
{
|
||||
@ -276,7 +277,7 @@ public class TachiyomiServiceTests
|
||||
.WithChapter(new ChapterBuilder("96").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder("1").WithIsSpecial(true).WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithChapter(new ChapterBuilder("21").WithPages(1).Build())
|
||||
@ -429,7 +430,7 @@ public class TachiyomiServiceTests
|
||||
.WithChapter(new ChapterBuilder("96").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder("1").WithIsSpecial(true).WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithChapter(new ChapterBuilder("3").WithPages(1).Build())
|
||||
@ -472,7 +473,7 @@ public class TachiyomiServiceTests
|
||||
.WithChapter(new ChapterBuilder("96").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder("1").WithIsSpecial(true).WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithChapter(new ChapterBuilder("3").WithPages(1).Build())
|
||||
@ -570,7 +571,7 @@ public class TachiyomiServiceTests
|
||||
.WithChapter(new ChapterBuilder("96").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder("1").WithIsSpecial(true).WithPages(1).Build())
|
||||
.WithChapter(new ChapterBuilder("1").WithPages(1).Build())
|
||||
.Build())
|
||||
.WithVolume(new VolumeBuilder("2")
|
||||
.WithChapter(new ChapterBuilder("21").WithPages(1).Build())
|
||||
|
19
API.Tests/Services/Test Data/ReadingListService/Annual.cbl
Normal file
19
API.Tests/Services/Test Data/ReadingListService/Annual.cbl
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0"?>
|
||||
<ReadingList xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>Fables</Name>
|
||||
<Books>
|
||||
<Book Series="Fables" Number="1" Volume="2002" Year="2002">
|
||||
<Id>5bd3dd55-2a85-4325-aefa-21e9f19b12c9</Id>
|
||||
</Book>
|
||||
<Book Series="Fables" Number="2" Volume="2002" Year="2002">
|
||||
<Id>3831761c-604a-4420-bed2-9f5ac4e94bd4</Id>
|
||||
</Book>
|
||||
<Book Series="Fables Annual" Number="1" Volume="2003" Year="2003" Format="Annual">
|
||||
<Id>23acefd4-1bc7-4c3c-99df-133045d1f266</Id>
|
||||
</Book>
|
||||
<Book Series="Fables" Number="3" Volume="2002" Year="2002">
|
||||
<Id>27a5d7db-9f7e-4be1-aca6-998a1cc1488f</Id>
|
||||
</Book>
|
||||
</Books>
|
||||
<Matchers />
|
||||
</ReadingList>
|
@ -53,30 +53,30 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="30.1.0" />
|
||||
<PackageReference Include="MailKit" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1">
|
||||
<PackageReference Include="CsvHelper" Version="31.0.2" />
|
||||
<PackageReference Include="MailKit" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
||||
<PackageReference Include="Docnet.Core" Version="2.6.0" />
|
||||
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
|
||||
<PackageReference Include="ExCSS" Version="4.2.4" />
|
||||
<PackageReference Include="ExCSS" Version="4.2.5" />
|
||||
<PackageReference Include="Flurl" Version="3.0.7" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||
<PackageReference Include="Hangfire" Version="1.8.9" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.7.0" />
|
||||
<PackageReference Include="Hangfire" Version="1.8.11" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.8.0" />
|
||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.58" />
|
||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.1" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.59" />
|
||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.9" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />
|
||||
@ -94,16 +94,16 @@
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.SignalR.Core" Version="0.1.2" />
|
||||
<PackageReference Include="SharpCompress" Version="0.36.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.2" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.19.0.84025">
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.21.0.86780">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.15" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.4.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.3" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using API.Extensions;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
namespace API.Comparators;
|
||||
@ -6,28 +7,28 @@ namespace API.Comparators;
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Sorts chapters based on their Number. Uses natural ordering of doubles.
|
||||
/// Sorts chapters based on their Number. Uses natural ordering of doubles. Specials always LAST.
|
||||
/// </summary>
|
||||
public class ChapterSortComparer : IComparer<double>
|
||||
public class ChapterSortComparerDefaultLast : IComparer<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Normal sort for 2 doubles. 0 always comes last
|
||||
/// Normal sort for 2 doubles. DefaultChapterNumber always comes last
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
public int Compare(double x, double y)
|
||||
public int Compare(float x, float y)
|
||||
{
|
||||
if (x == Parser.DefaultChapterNumber && y == Parser.DefaultChapterNumber) return 0;
|
||||
if (x.Is(Parser.DefaultChapterNumber) && y.Is(Parser.DefaultChapterNumber)) return 0;
|
||||
// if x is 0, it comes second
|
||||
if (x == Parser.DefaultChapterNumber) return 1;
|
||||
if (x.Is(Parser.DefaultChapterNumber)) return 1;
|
||||
// if y is 0, it comes second
|
||||
if (y == Parser.DefaultChapterNumber) return -1;
|
||||
if (y.Is(Parser.DefaultChapterNumber)) return -1;
|
||||
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
|
||||
public static readonly ChapterSortComparer Default = new ChapterSortComparer();
|
||||
public static readonly ChapterSortComparerDefaultLast Default = new ChapterSortComparerDefaultLast();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -37,33 +38,43 @@ public class ChapterSortComparer : IComparer<double>
|
||||
/// This is represented by Chapter 0, Chapter 81.
|
||||
/// </example>
|
||||
/// </summary>
|
||||
public class ChapterSortComparerZeroFirst : IComparer<double>
|
||||
public class ChapterSortComparerDefaultFirst : IComparer<float>
|
||||
{
|
||||
public int Compare(double x, double y)
|
||||
public int Compare(float x, float y)
|
||||
{
|
||||
if (x == Parser.DefaultChapterNumber && y == Parser.DefaultChapterNumber) return 0;
|
||||
if (x.Is(Parser.DefaultChapterNumber) && y.Is(Parser.DefaultChapterNumber)) return 0;
|
||||
// if x is 0, it comes first
|
||||
if (x == Parser.DefaultChapterNumber) return -1;
|
||||
if (x.Is(Parser.DefaultChapterNumber)) return -1;
|
||||
// if y is 0, it comes first
|
||||
if (y == Parser.DefaultChapterNumber) return 1;
|
||||
if (y.Is(Parser.DefaultChapterNumber)) return 1;
|
||||
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
|
||||
public static readonly ChapterSortComparerZeroFirst Default = new ChapterSortComparerZeroFirst();
|
||||
public static readonly ChapterSortComparerDefaultFirst Default = new ChapterSortComparerDefaultFirst();
|
||||
}
|
||||
|
||||
public class SortComparerZeroLast : IComparer<double>
|
||||
/// <summary>
|
||||
/// Sorts chapters based on their Number. Uses natural ordering of doubles. Specials always LAST.
|
||||
/// </summary>
|
||||
public class ChapterSortComparerSpecialsLast : IComparer<float>
|
||||
{
|
||||
public int Compare(double x, double y)
|
||||
/// <summary>
|
||||
/// Normal sort for 2 doubles. DefaultSpecialNumber always comes last
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
public int Compare(float x, float y)
|
||||
{
|
||||
if (x == Parser.DefaultChapterNumber && y == Parser.DefaultChapterNumber) return 0;
|
||||
// if x is 0, it comes last
|
||||
if (x == Parser.DefaultChapterNumber) return 1;
|
||||
// if y is 0, it comes last
|
||||
if (y == Parser.DefaultChapterNumber) return -1;
|
||||
if (x.Is(Parser.SpecialVolumeNumber) && y.Is(Parser.SpecialVolumeNumber)) return 0;
|
||||
// if x is 0, it comes second
|
||||
if (x.Is(Parser.SpecialVolumeNumber)) return 1;
|
||||
// if y is 0, it comes second
|
||||
if (y.Is(Parser.SpecialVolumeNumber)) return -1;
|
||||
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
public static readonly SortComparerZeroLast Default = new SortComparerZeroLast();
|
||||
|
||||
public static readonly ChapterSortComparerSpecialsLast Default = new ChapterSortComparerSpecialsLast();
|
||||
}
|
||||
|
@ -33,13 +33,14 @@ public class CblController : BaseApiController
|
||||
/// <param name="file">FormBody with parameter name of cbl</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("validate")]
|
||||
public async Task<ActionResult<CblImportSummaryDto>> ValidateCbl([FromForm(Name = "cbl")] IFormFile file)
|
||||
public async Task<ActionResult<CblImportSummaryDto>> ValidateCbl([FromForm(Name = "cbl")] IFormFile file,
|
||||
[FromForm(Name = "comicVineMatching")] bool comicVineMatching = false)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
try
|
||||
{
|
||||
var cbl = await SaveAndLoadCblFile(file);
|
||||
var importSummary = await _readingListService.ValidateCblFile(userId, cbl);
|
||||
var importSummary = await _readingListService.ValidateCblFile(userId, cbl, comicVineMatching);
|
||||
importSummary.FileName = file.FileName;
|
||||
return Ok(importSummary);
|
||||
}
|
||||
@ -83,13 +84,14 @@ public class CblController : BaseApiController
|
||||
/// <param name="dryRun">If true, will only emulate the import but not perform. This should be done to preview what will happen</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("import")]
|
||||
public async Task<ActionResult<CblImportSummaryDto>> ImportCbl([FromForm(Name = "cbl")] IFormFile file, [FromForm(Name = "dryRun")] bool dryRun = false)
|
||||
public async Task<ActionResult<CblImportSummaryDto>> ImportCbl([FromForm(Name = "cbl")] IFormFile file,
|
||||
[FromForm(Name = "dryRun")] bool dryRun = false, [FromForm(Name = "comicVineMatching")] bool comicVineMatching = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
var cbl = await SaveAndLoadCblFile(file);
|
||||
var importSummary = await _readingListService.CreateReadingListFromCbl(userId, cbl, dryRun);
|
||||
var importSummary = await _readingListService.CreateReadingListFromCbl(userId, cbl, dryRun, comicVineMatching);
|
||||
importSummary.FileName = file.FileName;
|
||||
return Ok(importSummary);
|
||||
} catch (ArgumentNullException)
|
||||
|
@ -140,7 +140,7 @@ public class DownloadController : BaseApiController
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume!.SeriesId);
|
||||
try
|
||||
{
|
||||
return await DownloadFiles(files, $"download_{User.GetUsername()}_c{chapterId}", $"{series!.Name} - Chapter {chapter.Number}.zip");
|
||||
return await DownloadFiles(files, $"download_{User.GetUsername()}_c{chapterId}", $"{series!.Name} - Chapter {chapter.GetNumberTitle()}.zip");
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
|
@ -32,7 +32,11 @@ public class LicenseController(
|
||||
public async Task<ActionResult<bool>> HasValidLicense(bool forceCheck = false)
|
||||
{
|
||||
var result = await licenseService.HasActiveLicense(forceCheck);
|
||||
await taskScheduler.ScheduleKavitaPlusTasks();
|
||||
if (result)
|
||||
{
|
||||
await taskScheduler.ScheduleKavitaPlusTasks();
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ public class OpdsController : BaseApiController
|
||||
};
|
||||
|
||||
private readonly FilterV2Dto _filterV2Dto = new FilterV2Dto();
|
||||
private readonly ChapterSortComparer _chapterSortComparer = ChapterSortComparer.Default;
|
||||
private readonly ChapterSortComparerDefaultLast _chapterSortComparerDefaultLast = ChapterSortComparerDefaultLast.Default;
|
||||
private const int PageSize = 20;
|
||||
|
||||
public OpdsController(IUnitOfWork unitOfWork, IDownloadService downloadService,
|
||||
@ -857,8 +857,8 @@ public class OpdsController : BaseApiController
|
||||
var seriesDetail = await _seriesService.GetSeriesDetail(seriesId, userId);
|
||||
foreach (var volume in seriesDetail.Volumes)
|
||||
{
|
||||
var chapters = (await _unitOfWork.ChapterRepository.GetChaptersAsync(volume.Id)).OrderBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture),
|
||||
_chapterSortComparer);
|
||||
var chapters = (await _unitOfWork.ChapterRepository.GetChaptersAsync(volume.Id))
|
||||
.OrderBy(x => x.MinNumber, _chapterSortComparerDefaultLast);
|
||||
|
||||
foreach (var chapterId in chapters.Select(c => c.Id))
|
||||
{
|
||||
@ -907,8 +907,8 @@ public class OpdsController : BaseApiController
|
||||
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
|
||||
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(volumeId);
|
||||
var chapters =
|
||||
(await _unitOfWork.ChapterRepository.GetChaptersAsync(volumeId)).OrderBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture),
|
||||
_chapterSortComparer);
|
||||
(await _unitOfWork.ChapterRepository.GetChaptersAsync(volumeId))
|
||||
.OrderBy(x => x.MinNumber, _chapterSortComparerDefaultLast);
|
||||
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {_seriesService.FormatChapterName(userId, libraryType)}s ",
|
||||
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}", apiKey, prefix);
|
||||
SetFeedId(feed, $"series-{series.Id}-volume-{volume.Id}-{_seriesService.FormatChapterName(userId, libraryType)}s");
|
||||
@ -1101,18 +1101,18 @@ public class OpdsController : BaseApiController
|
||||
|
||||
var title = $"{series.Name}";
|
||||
|
||||
if (volume!.Chapters.Count == 1)
|
||||
if (volume!.Chapters.Count == 1 && !volume.IsSpecial())
|
||||
{
|
||||
var volumeLabel = await _localizationService.Translate(userId, "volume-num", string.Empty);
|
||||
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType, volumeLabel);
|
||||
if (volume.Name != Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
|
||||
SeriesService.RenameVolumeName(volume, libraryType, volumeLabel);
|
||||
if (!volume.IsLooseLeaf())
|
||||
{
|
||||
title += $" - {volume.Name}";
|
||||
}
|
||||
}
|
||||
else if (!volume.IsLooseLeaf())
|
||||
else if (!volume.IsLooseLeaf() && !volume.IsSpecial())
|
||||
{
|
||||
title = $"{series.Name} - Volume {volume.Name} - {await _seriesService.FormatChapterTitle(userId, chapter, libraryType)}";
|
||||
title = $"{series.Name} - Volume {volume.Name} - {await _seriesService.FormatChapterTitle(userId, chapter, libraryType)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -13,14 +13,25 @@ public class ChapterDto : IHasReadTimeEstimate
|
||||
{
|
||||
public int Id { get; init; }
|
||||
/// <summary>
|
||||
/// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2".
|
||||
/// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2". If special, will be special name.
|
||||
/// </summary>
|
||||
/// <remarks>This can be something like 19.HU or Alpha as some comics are like this</remarks>
|
||||
public string Range { get; init; } = default!;
|
||||
/// <summary>
|
||||
/// Smallest number of the Range.
|
||||
/// </summary>
|
||||
[Obsolete("Use MinNumber and MaxNumber instead")]
|
||||
public string Number { get; init; } = default!;
|
||||
/// <summary>
|
||||
/// This may be 0 under the circumstance that the Issue is "Alpha" or other non-standard numbers.
|
||||
/// </summary>
|
||||
public float MinNumber { get; init; }
|
||||
public float MaxNumber { get; init; }
|
||||
/// <summary>
|
||||
/// The sorting order of the Chapter. Inherits from MinNumber, but can be overridden.
|
||||
/// </summary>
|
||||
public float SortOrder { get; set; }
|
||||
/// <summary>
|
||||
/// Total number of pages in all MangaFiles
|
||||
/// </summary>
|
||||
public int Pages { get; init; }
|
||||
|
@ -48,6 +48,9 @@ public enum FilterField
|
||||
/// <summary>
|
||||
/// Average rating from Kavita+ - Not usable for non-licensed users
|
||||
/// </summary>
|
||||
AverageRating = 28
|
||||
AverageRating = 28,
|
||||
Imprint = 29,
|
||||
Team = 30,
|
||||
Location = 31
|
||||
|
||||
}
|
||||
|
@ -18,10 +18,13 @@ public class ChapterMetadataDto
|
||||
public ICollection<PersonDto> Characters { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Pencillers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Inkers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Imprints { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Colorists { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Letterers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Editors { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Translators { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Teams { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Locations { get; set; } = new List<PersonDto>();
|
||||
|
||||
public ICollection<GenreTagDto> Genres { get; set; } = new List<GenreTagDto>();
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Xml.Serialization;
|
||||
using API.Data.Metadata;
|
||||
|
||||
namespace API.DTOs.ReadingLists.CBL;
|
||||
|
||||
@ -21,6 +22,12 @@ public class CblBook
|
||||
[XmlAttribute("Year")]
|
||||
public string Year { get; set; }
|
||||
/// <summary>
|
||||
/// Main Series, Annual, Limited Series
|
||||
/// </summary>
|
||||
/// <remarks>This maps to <see cref="ComicInfo">Format</see> tag</remarks>
|
||||
[XmlAttribute("Format")]
|
||||
public string Format { get; set; }
|
||||
/// <summary>
|
||||
/// The underlying filetype
|
||||
/// </summary>
|
||||
/// <remarks>This is not part of the standard and explicitly for Kavita to support non cbz/cbr files</remarks>
|
||||
|
@ -22,4 +22,5 @@ public class RelatedSeriesDto
|
||||
public IEnumerable<SeriesDto> Doujinshis { get; set; } = default!;
|
||||
public IEnumerable<SeriesDto> Parent { get; set; } = default!;
|
||||
public IEnumerable<SeriesDto> Editions { get; set; } = default!;
|
||||
public IEnumerable<SeriesDto> Annuals { get; set; } = default!;
|
||||
}
|
||||
|
@ -17,4 +17,5 @@ public class UpdateRelatedSeriesDto
|
||||
public IList<int> AlternativeVersions { get; set; } = default!;
|
||||
public IList<int> Doujinshis { get; set; } = default!;
|
||||
public IList<int> Editions { get; set; } = default!;
|
||||
public IList<int> Annuals { get; set; } = default!;
|
||||
}
|
||||
|
@ -30,10 +30,14 @@ public class SeriesMetadataDto
|
||||
public ICollection<PersonDto> Characters { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Pencillers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Inkers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Imprints { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Colorists { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Letterers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Editors { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Translators { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Teams { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Locations { get; set; } = new List<PersonDto>();
|
||||
|
||||
/// <summary>
|
||||
/// Highest Age Rating from all Chapters
|
||||
/// </summary>
|
||||
@ -80,10 +84,13 @@ public class SeriesMetadataDto
|
||||
public bool ColoristLocked { get; set; }
|
||||
public bool EditorLocked { get; set; }
|
||||
public bool InkerLocked { get; set; }
|
||||
public bool ImprintLocked { get; set; }
|
||||
public bool LettererLocked { get; set; }
|
||||
public bool PencillerLocked { get; set; }
|
||||
public bool PublisherLocked { get; set; }
|
||||
public bool TranslatorLocked { get; set; }
|
||||
public bool TeamLocked { get; set; }
|
||||
public bool LocationLocked { get; set; }
|
||||
public bool CoverArtistLocked { get; set; }
|
||||
public bool ReleaseYearLocked { get; set; }
|
||||
|
||||
|
@ -14,5 +14,5 @@ public class ReadHistoryEvent
|
||||
public required string SeriesName { get; set; } = default!;
|
||||
public DateTime ReadDate { get; set; }
|
||||
public int ChapterId { get; set; }
|
||||
public required string ChapterNumber { get; set; } = default!;
|
||||
public required float ChapterNumber { get; set; } = default!;
|
||||
}
|
||||
|
12
API/DTOs/TachiyomiChapterDto.cs
Normal file
12
API/DTOs/TachiyomiChapterDto.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace API.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// This is explicitly for Tachiyomi. Number field was removed in v0.8.0, but Tachiyomi needs it for the hacks.
|
||||
/// </summary>
|
||||
public class TachiyomiChapterDto : ChapterDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Smallest number of the Range.
|
||||
/// </summary>
|
||||
public string Number { get; init; } = default!;
|
||||
}
|
@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using API.Entities;
|
||||
using API.Entities.Interfaces;
|
||||
using API.Extensions;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
namespace API.DTOs;
|
||||
@ -20,7 +21,7 @@ public class VolumeDto : IHasReadTimeEstimate
|
||||
/// This will map to MinNumber. Number was removed in v0.7.13.8/v0.7.14
|
||||
/// </summary>
|
||||
[Obsolete("Use MinNumber")]
|
||||
public float Number { get; set; }
|
||||
public int Number { get; set; }
|
||||
public int Pages { get; set; }
|
||||
public int PagesRead { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
@ -50,6 +51,15 @@ public class VolumeDto : IHasReadTimeEstimate
|
||||
/// <returns></returns>
|
||||
public bool IsLooseLeaf()
|
||||
{
|
||||
return Math.Abs(this.MinNumber - Parser.LooseLeafVolumeNumber) < 0.001f;
|
||||
return MinNumber.Is(Parser.LooseLeafVolumeNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does this volume hold only specials?
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsSpecial()
|
||||
{
|
||||
return MinNumber.Is(Parser.SpecialVolumeNumber);
|
||||
}
|
||||
}
|
||||
|
@ -156,10 +156,15 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
|
||||
{
|
||||
if (e.FromQuery || e.Entry.State != EntityState.Added || e.Entry.Entity is not IEntityDate entity) return;
|
||||
|
||||
entity.Created = DateTime.Now;
|
||||
entity.LastModified = DateTime.Now;
|
||||
entity.CreatedUtc = DateTime.UtcNow;
|
||||
entity.LastModifiedUtc = DateTime.UtcNow;
|
||||
|
||||
// This allows for mocking
|
||||
if (entity.Created == DateTime.MinValue)
|
||||
{
|
||||
entity.Created = DateTime.Now;
|
||||
entity.CreatedUtc = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnEntityStateChanged(object? sender, EntityStateChangedEventArgs e)
|
||||
|
140
API/Data/ManualMigrations/ManualMigrateMixedSpecials.cs
Normal file
140
API/Data/ManualMigrations/ManualMigrateMixedSpecials.cs
Normal file
@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using API.Helpers.Builders;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
|
||||
public class UserProgressCsvRecord
|
||||
{
|
||||
public bool IsSpecial { get; set; }
|
||||
public int AppUserId { get; set; }
|
||||
public int PagesRead { get; set; }
|
||||
public string Range { get; set; }
|
||||
public string Number { get; set; }
|
||||
public float MinNumber { get; set; }
|
||||
public int SeriesId { get; set; }
|
||||
public int VolumeId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// v0.8.0 migration to move Specials into their own volume and retain user progress.
|
||||
/// </summary>
|
||||
public static class MigrateMixedSpecials
|
||||
{
|
||||
public static async Task Migrate(DataContext dataContext, IUnitOfWork unitOfWork, ILogger<Program> logger)
|
||||
{
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "ManualMigrateMixedSpecials"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogCritical(
|
||||
"Running ManualMigrateMixedSpecials migration - Please be patient, this may take some time. This is not an error");
|
||||
|
||||
// First, group all the progresses into different series
|
||||
|
||||
// Get each series and move the specials from old volume to the new Volume()
|
||||
|
||||
// Create a new progress event from existing and store the Id of existing progress event to delete it
|
||||
|
||||
// Save per series
|
||||
|
||||
var progress = await dataContext.AppUserProgresses
|
||||
.Join(dataContext.Chapter, p => p.ChapterId, c => c.Id, (p, c) => new UserProgressCsvRecord
|
||||
{
|
||||
IsSpecial = c.IsSpecial,
|
||||
AppUserId = p.AppUserId,
|
||||
PagesRead = p.PagesRead,
|
||||
Range = c.Range,
|
||||
Number = c.Number,
|
||||
MinNumber = c.MinNumber,
|
||||
SeriesId = p.SeriesId,
|
||||
VolumeId = p.VolumeId
|
||||
})
|
||||
.Where(d => d.IsSpecial || d.Number == "0")
|
||||
.Join(dataContext.Volume, d => d.VolumeId, v => v.Id, (d, v) => new
|
||||
{
|
||||
ProgressRecord = d,
|
||||
Volume = v
|
||||
})
|
||||
.Where(d => d.Volume.Name == "0")
|
||||
.ToListAsync();
|
||||
|
||||
// First, group all the progresses into different series
|
||||
logger.LogCritical("Migrating {Count} progress events to new Volume structure - This may take over 10 minutes depending on size of DB. Please wait", progress.Count);
|
||||
var progressesGroupedBySeries = progress.GroupBy(p => p.ProgressRecord.SeriesId);
|
||||
|
||||
foreach (var seriesGroup in progressesGroupedBySeries)
|
||||
{
|
||||
// Get each series and move the specials from the old volume to the new Volume
|
||||
var seriesId = seriesGroup.Key;
|
||||
var specialsInSeries = seriesGroup
|
||||
.Where(p => p.ProgressRecord.IsSpecial)
|
||||
.ToList();
|
||||
|
||||
|
||||
// Get distinct Volumes by Id. For each one, create it then create the progress events
|
||||
var distinctVolumes = specialsInSeries.DistinctBy(d => d.Volume.Id);
|
||||
foreach (var distinctVolume in distinctVolumes)
|
||||
{
|
||||
// Create a new volume for each series with the appropriate number (-100000)
|
||||
var chapters = await dataContext.Chapter
|
||||
.Where(c => c.VolumeId == distinctVolume.Volume.Id && c.IsSpecial).ToListAsync();
|
||||
|
||||
var newVolume = new VolumeBuilder(Parser.SpecialVolume)
|
||||
.WithSeriesId(seriesId)
|
||||
.WithChapters(chapters)
|
||||
.Build();
|
||||
dataContext.Volume.Add(newVolume);
|
||||
await dataContext.SaveChangesAsync(); // Save changes to generate the newVolumeId
|
||||
|
||||
// Migrate the progress event to the new volume
|
||||
distinctVolume.ProgressRecord.VolumeId = newVolume.Id;
|
||||
|
||||
|
||||
logger.LogInformation("Moving {Count} chapters from Volume Id {OldVolumeId} to New Volume {NewVolumeId}",
|
||||
chapters.Count, distinctVolume.Volume.Id, newVolume.Id);
|
||||
// Move the special chapters from the old volume to the new Volume
|
||||
var specialChapters = await dataContext.Chapter
|
||||
.Where(c => c.VolumeId == distinctVolume.ProgressRecord.VolumeId && c.IsSpecial)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var specialChapter in specialChapters)
|
||||
{
|
||||
// Update the VolumeId on the existing progress event
|
||||
specialChapter.VolumeId = newVolume.Id;
|
||||
}
|
||||
await dataContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// Save changes after processing all series
|
||||
if (dataContext.ChangeTracker.HasChanges())
|
||||
{
|
||||
await dataContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// Update all Volumes with Name as "0" -> Special
|
||||
logger.LogCritical("Updating all Volumes with Name 0 to SpecialNumber");
|
||||
|
||||
|
||||
|
||||
dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
|
||||
{
|
||||
Name = "ManualMigrateMixedSpecials",
|
||||
ProductVersion = BuildInfo.Version.ToString(),
|
||||
RanAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
await dataContext.SaveChangesAsync();
|
||||
logger.LogCritical(
|
||||
"Running ManualMigrateMixedSpecials migration - Completed. This is not an error");
|
||||
}
|
||||
}
|
89
API/Data/ManualMigrations/MigrateChapterFields.cs
Normal file
89
API/Data/ManualMigrations/MigrateChapterFields.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Introduced in v0.8.0, this migrates the existing Chapter and Volume 0 -> Parser defined, MangaFile.FileName
|
||||
/// </summary>
|
||||
public static class MigrateChapterFields
|
||||
{
|
||||
public static async Task Migrate(DataContext dataContext, IUnitOfWork unitOfWork, ILogger<Program> logger)
|
||||
{
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateChapterFields"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogCritical(
|
||||
"Running MigrateChapterFields migration - Please be patient, this may take some time. This is not an error");
|
||||
|
||||
// Update all volumes only have specials in them (rare)
|
||||
var volumesWithJustSpecials = dataContext.Volume
|
||||
.Include(v => v.Chapters)
|
||||
.Where(v => v.Name == "0" && v.Chapters.All(c => c.IsSpecial))
|
||||
.ToList();
|
||||
logger.LogCritical(
|
||||
"Running MigrateChapterFields migration - Updating {Count} volumes that only have specials in them", volumesWithJustSpecials.Count);
|
||||
foreach (var volume in volumesWithJustSpecials)
|
||||
{
|
||||
volume.Name = $"{Parser.SpecialVolumeNumber}";
|
||||
volume.MinNumber = Parser.SpecialVolumeNumber;
|
||||
volume.MaxNumber = Parser.SpecialVolumeNumber;
|
||||
}
|
||||
|
||||
// Update all volumes that only have loose leafs in them
|
||||
var looseLeafVolumes = dataContext.Volume
|
||||
.Include(v => v.Chapters)
|
||||
.Where(v => v.Name == "0" && v.Chapters.All(c => !c.IsSpecial))
|
||||
.ToList();
|
||||
logger.LogCritical(
|
||||
"Running MigrateChapterFields migration - Updating {Count} volumes that only have loose leaf chapters in them", looseLeafVolumes.Count);
|
||||
foreach (var volume in looseLeafVolumes)
|
||||
{
|
||||
volume.Name = $"{Parser.DefaultChapterNumber}";
|
||||
volume.MinNumber = Parser.DefaultChapterNumber;
|
||||
volume.MaxNumber = Parser.DefaultChapterNumber;
|
||||
}
|
||||
|
||||
// Update all MangaFile
|
||||
logger.LogCritical(
|
||||
"Running MigrateChapterFields migration - Updating all MangaFiles");
|
||||
foreach (var mangaFile in dataContext.MangaFile)
|
||||
{
|
||||
mangaFile.FileName = Parser.RemoveExtensionIfSupported(mangaFile.FilePath);
|
||||
}
|
||||
|
||||
var looseLeafChapters = await dataContext.Chapter.Where(c => c.Number == "0").ToListAsync();
|
||||
logger.LogCritical(
|
||||
"Running MigrateChapterFields migration - Updating {Count} loose leaf chapters", looseLeafChapters.Count);
|
||||
foreach (var chapter in looseLeafChapters)
|
||||
{
|
||||
chapter.Number = Parser.DefaultChapter;
|
||||
chapter.MinNumber = Parser.DefaultChapterNumber;
|
||||
chapter.MaxNumber = Parser.DefaultChapterNumber;
|
||||
}
|
||||
|
||||
dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
|
||||
{
|
||||
Name = "MigrateChapterFields",
|
||||
ProductVersion = BuildInfo.Version.ToString(),
|
||||
RanAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
await dataContext.SaveChangesAsync();
|
||||
|
||||
|
||||
logger.LogCritical(
|
||||
"Running MigrateChapterFields migration - Completed. This is not an error");
|
||||
}
|
||||
}
|
50
API/Data/ManualMigrations/MigrateChapterNumber.cs
Normal file
50
API/Data/ManualMigrations/MigrateChapterNumber.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// Introduced in v0.8.0, this migrates the existing Chapter Range -> Chapter Min/Max Number
|
||||
/// </summary>
|
||||
public static class MigrateChapterNumber
|
||||
{
|
||||
public static async Task Migrate(DataContext dataContext, ILogger<Program> logger)
|
||||
{
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateChapterNumber"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogCritical(
|
||||
"Running MigrateChapterNumber migration - Please be patient, this may take some time. This is not an error");
|
||||
|
||||
// Get all volumes
|
||||
foreach (var chapter in dataContext.Chapter)
|
||||
{
|
||||
if (chapter.IsSpecial)
|
||||
{
|
||||
chapter.MinNumber = Parser.DefaultChapterNumber;
|
||||
chapter.MaxNumber = Parser.DefaultChapterNumber;
|
||||
continue;
|
||||
}
|
||||
chapter.MinNumber = Parser.MinNumberFromRange(chapter.Range);
|
||||
chapter.MaxNumber = Parser.MaxNumberFromRange(chapter.Range);
|
||||
}
|
||||
|
||||
dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
|
||||
{
|
||||
Name = "MigrateChapterNumber",
|
||||
ProductVersion = BuildInfo.Version.ToString(),
|
||||
RanAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
await dataContext.SaveChangesAsync();
|
||||
logger.LogCritical(
|
||||
"Running MigrateChapterNumber migration - Completed. This is not an error");
|
||||
}
|
||||
}
|
55
API/Data/ManualMigrations/MigrateChapterRange.cs
Normal file
55
API/Data/ManualMigrations/MigrateChapterRange.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using API.Helpers.Builders;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// v0.8.0 changed the range to that it doesn't have filename by default
|
||||
/// </summary>
|
||||
public static class MigrateChapterRange
|
||||
{
|
||||
public static async Task Migrate(DataContext dataContext, IUnitOfWork unitOfWork, ILogger<Program> logger)
|
||||
{
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateChapterRange"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogCritical(
|
||||
"Running MigrateChapterRange migration - Please be patient, this may take some time. This is not an error");
|
||||
|
||||
var chapters = await dataContext.Chapter.ToListAsync();
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
if (Parser.MinNumberFromRange(chapter.Range) == 0.0f)
|
||||
{
|
||||
chapter.Range = chapter.GetNumberTitle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save changes after processing all series
|
||||
if (dataContext.ChangeTracker.HasChanges())
|
||||
{
|
||||
await dataContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
|
||||
{
|
||||
Name = "MigrateChapterRange",
|
||||
ProductVersion = BuildInfo.Version.ToString(),
|
||||
RanAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
await dataContext.SaveChangesAsync();
|
||||
logger.LogCritical(
|
||||
"Running MigrateChapterRange migration - Completed. This is not an error");
|
||||
}
|
||||
}
|
@ -15,9 +15,8 @@ public static class MigrateLibrariesToHaveAllFileTypes
|
||||
{
|
||||
public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, ILogger<Program> logger)
|
||||
{
|
||||
if (await dataContext.Library.AnyAsync(l => l.LibraryFileTypes.Count == 0))
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateLibrariesToHaveAllFileTypes"))
|
||||
{
|
||||
logger.LogCritical("Running MigrateLibrariesToHaveAllFileTypes migration - Completed. This is not an error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -16,8 +16,6 @@ public static class MigrateManualHistory
|
||||
{
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync())
|
||||
{
|
||||
logger.LogCritical(
|
||||
"Running MigrateManualHistory migration - Completed. This is not an error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs.Filtering.v2;
|
||||
using API.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
@ -21,8 +22,12 @@ public static class MigrateSmartFilterEncoding
|
||||
|
||||
public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, ILogger<Program> logger)
|
||||
{
|
||||
logger.LogCritical("Running MigrateSmartFilterEncoding migration - Please be patient, this may take some time. This is not an error");
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateSmartFilterEncoding"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogCritical("Running MigrateSmartFilterEncoding migration - Please be patient, this may take some time. This is not an error");
|
||||
|
||||
var smartFilters = dataContext.AppUserSmartFilter.ToList();
|
||||
foreach (var filter in smartFilters)
|
||||
|
@ -14,6 +14,10 @@ public static class MigrateUserLibrarySideNavStream
|
||||
{
|
||||
public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, ILogger<Program> logger)
|
||||
{
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateUserLibrarySideNavStream"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var usersWithLibraryStreams = await dataContext.AppUser
|
||||
.Include(u => u.SideNavStreams)
|
||||
|
41
API/Data/ManualMigrations/MigrateVolumeLookupName.cs
Normal file
41
API/Data/ManualMigrations/MigrateVolumeLookupName.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
|
||||
public static class MigrateVolumeLookupName
|
||||
{
|
||||
public static async Task Migrate(DataContext dataContext, IUnitOfWork unitOfWork, ILogger<Program> logger)
|
||||
{
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateVolumeLookupName"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogCritical(
|
||||
"Running MigrateVolumeLookupName migration - Please be patient, this may take some time. This is not an error");
|
||||
|
||||
// Update all volumes to have LookupName as after this migration, name isn't used for lookup
|
||||
var volumes = dataContext.Volume.ToList();
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
volume.LookupName = volume.Name;
|
||||
}
|
||||
|
||||
dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
|
||||
{
|
||||
Name = "MigrateVolumeLookupName",
|
||||
ProductVersion = BuildInfo.Version.ToString(),
|
||||
RanAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
await dataContext.SaveChangesAsync();
|
||||
logger.LogCritical(
|
||||
"Running MigrateVolumeLookupName migration - Completed. This is not an error");
|
||||
}
|
||||
}
|
@ -13,8 +13,13 @@ namespace API.Data.ManualMigrations;
|
||||
/// </summary>
|
||||
public static class MigrateVolumeNumber
|
||||
{
|
||||
public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, ILogger<Program> logger)
|
||||
public static async Task Migrate(DataContext dataContext, ILogger<Program> logger)
|
||||
{
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateVolumeNumber"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await dataContext.Volume.AnyAsync(v => v.MaxNumber > 0))
|
||||
{
|
||||
logger.LogCritical(
|
||||
|
@ -20,6 +20,11 @@ public static class MigrateWantToReadExport
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateWantToReadExport"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var importFile = Path.Join(directoryService.ConfigDirectory, "want-to-read-migration.csv");
|
||||
if (File.Exists(importFile))
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using API.Data.Repositories;
|
||||
using API.Entities;
|
||||
using API.Services;
|
||||
using CsvHelper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
@ -15,8 +16,14 @@ namespace API.Data.ManualMigrations;
|
||||
/// </summary>
|
||||
public static class MigrateWantToReadImport
|
||||
{
|
||||
public static async Task Migrate(IUnitOfWork unitOfWork, IDirectoryService directoryService, ILogger<Program> logger)
|
||||
public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, IDirectoryService directoryService, ILogger<Program> logger)
|
||||
{
|
||||
|
||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateWantToReadImport"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var importFile = Path.Join(directoryService.ConfigDirectory, "want-to-read-migration.csv");
|
||||
var outputFile = Path.Join(directoryService.ConfigDirectory, "imported-want-to-read-migration.csv");
|
||||
|
||||
|
@ -127,7 +127,11 @@ public class ComicInfo
|
||||
public string CoverArtist { get; set; } = string.Empty;
|
||||
public string Editor { get; set; } = string.Empty;
|
||||
public string Publisher { get; set; } = string.Empty;
|
||||
public string Imprint { get; set; } = string.Empty;
|
||||
public string Characters { get; set; } = string.Empty;
|
||||
public string Teams { get; set; } = string.Empty;
|
||||
public string Locations { get; set; } = string.Empty;
|
||||
|
||||
|
||||
public static AgeRating ConvertAgeRatingToEnum(string value)
|
||||
{
|
||||
@ -151,9 +155,12 @@ public class ComicInfo
|
||||
info.Letterer = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.Letterer);
|
||||
info.Penciller = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.Penciller);
|
||||
info.Publisher = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.Publisher);
|
||||
info.Imprint = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.Imprint);
|
||||
info.Characters = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.Characters);
|
||||
info.Translator = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.Translator);
|
||||
info.CoverArtist = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.CoverArtist);
|
||||
info.Teams = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.Teams);
|
||||
info.Locations = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.Locations);
|
||||
|
||||
// We need to convert GTIN to ISBN
|
||||
if (!string.IsNullOrEmpty(info.GTIN))
|
||||
@ -174,7 +181,12 @@ public class ComicInfo
|
||||
|
||||
if (!string.IsNullOrEmpty(info.Number))
|
||||
{
|
||||
info.Number = info.Number.Replace(",", "."); // Corrective measure for non English OSes
|
||||
info.Number = info.Number.Trim().Replace(",", "."); // Corrective measure for non English OSes
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(info.Volume))
|
||||
{
|
||||
info.Volume = info.Volume.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
|
2877
API/Data/Migrations/20240214232436_ChapterNumber.Designer.cs
generated
Normal file
2877
API/Data/Migrations/20240214232436_ChapterNumber.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
API/Data/Migrations/20240214232436_ChapterNumber.cs
Normal file
40
API/Data/Migrations/20240214232436_ChapterNumber.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChapterNumber : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<float>(
|
||||
name: "MaxNumber",
|
||||
table: "Chapter",
|
||||
type: "REAL",
|
||||
nullable: false,
|
||||
defaultValue: 0f);
|
||||
|
||||
migrationBuilder.AddColumn<float>(
|
||||
name: "MinNumber",
|
||||
table: "Chapter",
|
||||
type: "REAL",
|
||||
nullable: false,
|
||||
defaultValue: 0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxNumber",
|
||||
table: "Chapter");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MinNumber",
|
||||
table: "Chapter");
|
||||
}
|
||||
}
|
||||
}
|
2880
API/Data/Migrations/20240216000223_MangaFileNameTemp.Designer.cs
generated
Normal file
2880
API/Data/Migrations/20240216000223_MangaFileNameTemp.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
API/Data/Migrations/20240216000223_MangaFileNameTemp.cs
Normal file
28
API/Data/Migrations/20240216000223_MangaFileNameTemp.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MangaFileNameTemp : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "FileName",
|
||||
table: "MangaFile",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FileName",
|
||||
table: "MangaFile");
|
||||
}
|
||||
}
|
||||
}
|
2883
API/Data/Migrations/20240222125420_ChapterIssueSort.Designer.cs
generated
Normal file
2883
API/Data/Migrations/20240222125420_ChapterIssueSort.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
API/Data/Migrations/20240222125420_ChapterIssueSort.cs
Normal file
29
API/Data/Migrations/20240222125420_ChapterIssueSort.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChapterIssueSort : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<float>(
|
||||
name: "SortOrder",
|
||||
table: "Chapter",
|
||||
type: "REAL",
|
||||
nullable: false,
|
||||
defaultValue: 0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SortOrder",
|
||||
table: "Chapter");
|
||||
}
|
||||
}
|
||||
}
|
2886
API/Data/Migrations/20240225235816_VolumeLookupName.Designer.cs
generated
Normal file
2886
API/Data/Migrations/20240225235816_VolumeLookupName.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
API/Data/Migrations/20240225235816_VolumeLookupName.cs
Normal file
28
API/Data/Migrations/20240225235816_VolumeLookupName.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class VolumeLookupName : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "LookupName",
|
||||
table: "Volume",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LookupName",
|
||||
table: "Volume");
|
||||
}
|
||||
}
|
||||
}
|
2889
API/Data/Migrations/20240309140117_SeriesImprints.Designer.cs
generated
Normal file
2889
API/Data/Migrations/20240309140117_SeriesImprints.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
API/Data/Migrations/20240309140117_SeriesImprints.cs
Normal file
29
API/Data/Migrations/20240309140117_SeriesImprints.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SeriesImprints : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "ImprintLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ImprintLocked",
|
||||
table: "SeriesMetadata");
|
||||
}
|
||||
}
|
||||
}
|
2892
API/Data/Migrations/20240313112552_SeriesLowestFolderPath.Designer.cs
generated
Normal file
2892
API/Data/Migrations/20240313112552_SeriesLowestFolderPath.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
API/Data/Migrations/20240313112552_SeriesLowestFolderPath.cs
Normal file
28
API/Data/Migrations/20240313112552_SeriesLowestFolderPath.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SeriesLowestFolderPath : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "LowestFolderPath",
|
||||
table: "Series",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LowestFolderPath",
|
||||
table: "Series");
|
||||
}
|
||||
}
|
||||
}
|
2898
API/Data/Migrations/20240314194402_TeamsAndLocations.Designer.cs
generated
Normal file
2898
API/Data/Migrations/20240314194402_TeamsAndLocations.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
API/Data/Migrations/20240314194402_TeamsAndLocations.cs
Normal file
40
API/Data/Migrations/20240314194402_TeamsAndLocations.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class TeamsAndLocations : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "LocationLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "TeamLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LocationLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TeamLocked",
|
||||
table: "SeriesMetadata");
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ namespace API.Data.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.3");
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||
{
|
||||
@ -679,9 +679,15 @@ namespace API.Data.Migrations
|
||||
b.Property<int>("MaxHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float>("MaxNumber")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("MinHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float>("MinNumber")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("Number")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -697,6 +703,9 @@ namespace API.Data.Migrations
|
||||
b.Property<string>("SeriesGroup")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("SortOrder")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("StoryArc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -973,6 +982,9 @@ namespace API.Data.Migrations
|
||||
b.Property<string>("Extension")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FilePath")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -1241,6 +1253,9 @@ namespace API.Data.Migrations
|
||||
b.Property<bool>("GenresLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ImprintLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("InkerLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -1253,6 +1268,9 @@ namespace API.Data.Migrations
|
||||
b.Property<bool>("LettererLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("LocationLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MaxCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -1290,6 +1308,9 @@ namespace API.Data.Migrations
|
||||
b.Property<bool>("TagsLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TeamLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TotalCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -1665,6 +1686,9 @@ namespace API.Data.Migrations
|
||||
b.Property<bool>("LocalizedNameLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LowestFolderPath")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("MaxHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -1839,6 +1863,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LookupName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("MaxHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -167,9 +167,10 @@ public class AppUserProgressRepository : IAppUserProgressRepository
|
||||
(appUserProgresses, chapter) => new {appUserProgresses, chapter})
|
||||
.Where(p => p.appUserProgresses.SeriesId == seriesId && p.appUserProgresses.AppUserId == userId &&
|
||||
p.appUserProgresses.PagesRead >= p.chapter.Pages)
|
||||
.Select(p => p.chapter.Range)
|
||||
.Where(p => p.chapter.MaxNumber != Parser.SpecialVolumeNumber)
|
||||
.Select(p => p.chapter.MaxNumber)
|
||||
.ToListAsync();
|
||||
return list.Count == 0 ? 0 : list.DefaultIfEmpty().Where(d => d != null).Max(d => (int) Math.Floor(Parser.MaxNumberFromRange(d)));
|
||||
return list.Count == 0 ? 0 : (int) list.DefaultIfEmpty().Max(d => d);
|
||||
}
|
||||
|
||||
public async Task<float> GetHighestFullyReadVolumeForSeries(int seriesId, int userId)
|
||||
@ -179,6 +180,7 @@ public class AppUserProgressRepository : IAppUserProgressRepository
|
||||
(appUserProgresses, chapter) => new {appUserProgresses, chapter})
|
||||
.Where(p => p.appUserProgresses.SeriesId == seriesId && p.appUserProgresses.AppUserId == userId &&
|
||||
p.appUserProgresses.PagesRead >= p.chapter.Pages)
|
||||
.Where(p => p.chapter.MaxNumber != Parser.SpecialVolumeNumber)
|
||||
.Select(p => p.chapter.Volume.MaxNumber)
|
||||
.ToListAsync();
|
||||
return list.Count == 0 ? 0 : list.DefaultIfEmpty().Max();
|
||||
|
@ -78,7 +78,7 @@ public class ChapterRepository : IChapterRepository
|
||||
.Where(c => c.Id == chapterId)
|
||||
.Join(_context.Volume, c => c.VolumeId, v => v.Id, (chapter, volume) => new
|
||||
{
|
||||
ChapterNumber = chapter.Range,
|
||||
ChapterNumber = chapter.MinNumber,
|
||||
VolumeNumber = volume.Name,
|
||||
VolumeId = volume.Id,
|
||||
chapter.IsSpecial,
|
||||
@ -102,8 +102,8 @@ public class ChapterRepository : IChapterRepository
|
||||
})
|
||||
.Select(data => new ChapterInfoDto()
|
||||
{
|
||||
ChapterNumber = data.ChapterNumber,
|
||||
VolumeNumber = data.VolumeNumber + string.Empty,
|
||||
ChapterNumber = data.ChapterNumber + string.Empty, // TODO: Fix this
|
||||
VolumeNumber = data.VolumeNumber + string.Empty, // TODO: Fix this
|
||||
VolumeId = data.VolumeId,
|
||||
IsSpecial = data.IsSpecial,
|
||||
SeriesId = data.SeriesId,
|
||||
@ -175,6 +175,7 @@ public class ChapterRepository : IChapterRepository
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Includes(includes)
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.FirstOrDefaultAsync(c => c.Id == chapterId);
|
||||
}
|
||||
|
||||
@ -187,6 +188,7 @@ public class ChapterRepository : IChapterRepository
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => c.VolumeId == volumeId)
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
@ -267,10 +269,16 @@ public class ChapterRepository : IChapterRepository
|
||||
return chapter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Includes Volumes
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Chapter> GetChaptersForSeries(int seriesId)
|
||||
{
|
||||
return _context.Chapter
|
||||
.Where(c => c.Volume.SeriesId == seriesId)
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.Include(c => c.Volume)
|
||||
.AsEnumerable();
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ public interface IExternalSeriesMetadataRepository
|
||||
Task<bool> ExternalSeriesMetadataNeedsRefresh(int seriesId);
|
||||
Task<SeriesDetailPlusDto> GetSeriesDetailPlusDto(int seriesId);
|
||||
Task LinkRecommendationsToSeries(Series series);
|
||||
Task LinkRecommendationsToSeries(int seriesId);
|
||||
Task<bool> IsBlacklistedSeries(int seriesId);
|
||||
Task CreateBlacklistedSeries(int seriesId, bool saveChanges = true);
|
||||
Task RemoveFromBlacklist(int seriesId);
|
||||
@ -179,6 +180,13 @@ public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepositor
|
||||
return seriesDetailPlusDto;
|
||||
}
|
||||
|
||||
public async Task LinkRecommendationsToSeries(int seriesId)
|
||||
{
|
||||
var series = await _context.Series.Where(s => s.Id == seriesId).AsNoTracking().SingleOrDefaultAsync();
|
||||
if (series == null) return;
|
||||
await LinkRecommendationsToSeries(series);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches Recommendations without a SeriesId on record and attempts to link based on Series Name/Localized Name
|
||||
/// </summary>
|
||||
|
@ -318,7 +318,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
/// <returns></returns>
|
||||
public async Task<bool> DoAnySeriesFoldersMatch(IEnumerable<string> folders)
|
||||
{
|
||||
var normalized = folders.Select(Services.Tasks.Scanner.Parser.Parser.NormalizePath);
|
||||
var normalized = folders.Select(Parser.NormalizePath);
|
||||
return await _context.Series.AnyAsync(s => normalized.Contains(s.FolderPath));
|
||||
}
|
||||
|
||||
|
@ -498,6 +498,7 @@ public class SeriesRepository : ISeriesRepository
|
||||
.Include(c => c.Files)
|
||||
.Where(c => EF.Functions.Like(c.TitleName, $"%{searchQuery}%")
|
||||
|| EF.Functions.Like(c.ISBN, $"%{searchQuery}%")
|
||||
|| EF.Functions.Like(c.Range, $"%{searchQuery}%")
|
||||
)
|
||||
.Where(c => c.Files.All(f => fileIds.Contains(f.Id)))
|
||||
.AsSplitQuery()
|
||||
@ -1183,6 +1184,9 @@ public class SeriesRepository : ISeriesRepository
|
||||
FilterField.Letterer => query.HasPeople(true, statement.Comparison, (IList<int>) value),
|
||||
FilterField.Colorist => query.HasPeople(true, statement.Comparison, (IList<int>) value),
|
||||
FilterField.Inker => query.HasPeople(true, statement.Comparison, (IList<int>) value),
|
||||
FilterField.Imprint => query.HasPeople(true, statement.Comparison, (IList<int>) value),
|
||||
FilterField.Team => query.HasPeople(true, statement.Comparison, (IList<int>) value),
|
||||
FilterField.Location => query.HasPeople(true, statement.Comparison, (IList<int>) value),
|
||||
FilterField.Penciller => query.HasPeople(true, statement.Comparison, (IList<int>) value),
|
||||
FilterField.Writers => query.HasPeople(true, statement.Comparison, (IList<int>) value),
|
||||
FilterField.Genres => query.HasGenre(true, statement.Comparison, (IList<int>) value),
|
||||
@ -1817,19 +1821,7 @@ public class SeriesRepository : ISeriesRepository
|
||||
AlternativeSettings = await GetRelatedSeriesQuery(seriesId, usersSeriesIds, RelationKind.AlternativeSetting, userRating),
|
||||
AlternativeVersions = await GetRelatedSeriesQuery(seriesId, usersSeriesIds, RelationKind.AlternativeVersion, userRating),
|
||||
Doujinshis = await GetRelatedSeriesQuery(seriesId, usersSeriesIds, RelationKind.Doujinshi, userRating),
|
||||
// Parent = await _context.Series
|
||||
// .SelectMany(s =>
|
||||
// s.TargetSeries.Where(r => r.TargetSeriesId == seriesId
|
||||
// && usersSeriesIds.Contains(r.TargetSeriesId)
|
||||
// && r.RelationKind != RelationKind.Prequel
|
||||
// && r.RelationKind != RelationKind.Sequel
|
||||
// && r.RelationKind != RelationKind.Edition)
|
||||
// .Select(sr => sr.Series))
|
||||
// .RestrictAgainstAgeRestriction(userRating)
|
||||
// .AsSplitQuery()
|
||||
// .AsNoTracking()
|
||||
// .ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
// .ToListAsync(),
|
||||
Annuals = await GetRelatedSeriesQuery(seriesId, usersSeriesIds, RelationKind.Annual, userRating),
|
||||
Parent = await _context.SeriesRelation
|
||||
.Where(r => r.TargetSeriesId == seriesId
|
||||
&& usersSeriesIds.Contains(r.TargetSeriesId)
|
||||
@ -1891,8 +1883,8 @@ public class SeriesRepository : ISeriesRepository
|
||||
VolumeId = c.VolumeId,
|
||||
ChapterId = c.Id,
|
||||
Format = c.Volume.Series.Format,
|
||||
ChapterNumber = c.Number,
|
||||
ChapterRange = c.Range,
|
||||
ChapterNumber = c.MinNumber + string.Empty, // TODO: Refactor this
|
||||
ChapterRange = c.Range, // TODO: Refactor this
|
||||
IsSpecial = c.IsSpecial,
|
||||
VolumeNumber = c.Volume.MinNumber,
|
||||
ChapterTitle = c.Title,
|
||||
@ -2063,7 +2055,7 @@ public class SeriesRepository : ISeriesRepository
|
||||
foreach (var series in info)
|
||||
{
|
||||
if (series.FolderPath == null) continue;
|
||||
if (!map.ContainsKey(series.FolderPath))
|
||||
if (!map.TryGetValue(series.FolderPath, out var value))
|
||||
{
|
||||
map.Add(series.FolderPath, new List<SeriesModified>()
|
||||
{
|
||||
@ -2072,9 +2064,8 @@ public class SeriesRepository : ISeriesRepository
|
||||
}
|
||||
else
|
||||
{
|
||||
map[series.FolderPath].Add(series);
|
||||
value.Add(series);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return map;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -6,6 +7,7 @@ using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Extensions.QueryExtensions;
|
||||
using API.Services;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
@ -14,6 +16,15 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
[Flags]
|
||||
public enum VolumeIncludes
|
||||
{
|
||||
None = 1,
|
||||
Chapters = 2,
|
||||
People = 4,
|
||||
Tags = 8,
|
||||
}
|
||||
|
||||
public interface IVolumeRepository
|
||||
{
|
||||
void Add(Volume volume);
|
||||
@ -22,7 +33,7 @@ public interface IVolumeRepository
|
||||
Task<IList<MangaFile>> GetFilesForVolume(int volumeId);
|
||||
Task<string?> GetVolumeCoverImageAsync(int volumeId);
|
||||
Task<IList<int>> GetChapterIdsByVolumeIds(IReadOnlyList<int> volumeIds);
|
||||
Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId);
|
||||
Task<IList<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId, VolumeIncludes includes = VolumeIncludes.Chapters);
|
||||
Task<Volume?> GetVolumeAsync(int volumeId);
|
||||
Task<VolumeDto?> GetVolumeDtoAsync(int volumeId, int userId);
|
||||
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> seriesIds, bool includeChapters = false);
|
||||
@ -129,6 +140,7 @@ public class VolumeRepository : IVolumeRepository
|
||||
.Include(vol => vol.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.OrderBy(v => v.MinNumber)
|
||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync(vol => vol.Id == volumeId);
|
||||
|
||||
@ -177,22 +189,22 @@ public class VolumeRepository : IVolumeRepository
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId)
|
||||
public async Task<IList<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId, VolumeIncludes includes = VolumeIncludes.Chapters)
|
||||
{
|
||||
var volumes = await _context.Volume
|
||||
.Where(vol => vol.SeriesId == seriesId)
|
||||
.Include(vol => vol.Chapters)
|
||||
.ThenInclude(c => c.People)
|
||||
.Include(vol => vol.Chapters)
|
||||
.ThenInclude(c => c.Tags)
|
||||
.Includes(includes)
|
||||
.OrderBy(volume => volume.MinNumber)
|
||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
|
||||
await AddVolumeModifiers(userId, volumes);
|
||||
SortSpecialChapters(volumes);
|
||||
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
volume.Chapters = volume.Chapters.OrderBy(c => c.SortOrder).ToList();
|
||||
}
|
||||
|
||||
return volumes;
|
||||
}
|
||||
@ -213,15 +225,6 @@ public class VolumeRepository : IVolumeRepository
|
||||
}
|
||||
|
||||
|
||||
private static void SortSpecialChapters(IEnumerable<VolumeDto> volumes)
|
||||
{
|
||||
foreach (var v in volumes.WhereLooseLeaf())
|
||||
{
|
||||
v.Chapters = v.Chapters.OrderByNatural(x => x.Range).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task AddVolumeModifiers(int userId, IReadOnlyCollection<VolumeDto> volumes)
|
||||
{
|
||||
var volIds = volumes.Select(s => s.Id);
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Interfaces;
|
||||
using API.Extensions;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
namespace API.Entities;
|
||||
@ -10,14 +12,27 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate
|
||||
{
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Range of numbers. Chapter 2-4 -> "2-4". Chapter 2 -> "2".
|
||||
/// Range of numbers. Chapter 2-4 -> "2-4". Chapter 2 -> "2". If the chapter is a special, will return the Special Name
|
||||
/// </summary>
|
||||
public required string Range { get; set; }
|
||||
/// <summary>
|
||||
/// Smallest number of the Range. Can be a partial like Chapter 4.5
|
||||
/// </summary>
|
||||
[Obsolete("Use MinNumber and MaxNumber instead")]
|
||||
public required string Number { get; set; }
|
||||
/// <summary>
|
||||
/// Minimum Chapter Number.
|
||||
/// </summary>
|
||||
public float MinNumber { get; set; }
|
||||
/// <summary>
|
||||
/// Maximum Chapter Number
|
||||
/// </summary>
|
||||
public float MaxNumber { get; set; }
|
||||
/// <summary>
|
||||
/// The sorting order of the Chapter. Inherits from MinNumber, but can be overridden.
|
||||
/// </summary>
|
||||
public float SortOrder { get; set; }
|
||||
/// <summary>
|
||||
/// The files that represent this Chapter
|
||||
/// </summary>
|
||||
public ICollection<MangaFile> Files { get; set; } = null!;
|
||||
@ -44,6 +59,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate
|
||||
/// Used for books/specials to display custom title. For non-specials/books, will be set to <see cref="Range"/>
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Age Rating for the issue/chapter
|
||||
/// </summary>
|
||||
@ -130,10 +146,48 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate
|
||||
if (IsSpecial)
|
||||
{
|
||||
Number = Parser.DefaultChapter;
|
||||
MinNumber = Parser.DefaultChapterNumber;
|
||||
MaxNumber = Parser.DefaultChapterNumber;
|
||||
}
|
||||
// NOTE: This doesn't work well for all because Pdf usually should use into.Title or even filename
|
||||
Title = (IsSpecial && info.Format == MangaFormat.Epub)
|
||||
? info.Title
|
||||
: Range;
|
||||
: Parser.RemoveExtensionIfSupported(Range);
|
||||
|
||||
var specialTreatment = info.IsSpecialInfo();
|
||||
Range = specialTreatment ? info.Filename : info.Chapters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Chapter Number. If the chapter is a range, returns that, formatted.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetNumberTitle()
|
||||
{
|
||||
if (MinNumber.Is(MaxNumber))
|
||||
{
|
||||
if (MinNumber.Is(Parser.DefaultChapterNumber) && IsSpecial)
|
||||
{
|
||||
return Parser.RemoveExtensionIfSupported(Title);
|
||||
}
|
||||
|
||||
if (MinNumber.Is(0) && !float.TryParse(Range, out _))
|
||||
{
|
||||
return $"{Range}";
|
||||
}
|
||||
|
||||
return $"{MinNumber}";
|
||||
|
||||
}
|
||||
return $"{MinNumber}-{MaxNumber}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the Chapter representing a single Volume (volume 1.cbz). If so, Min/Max will be Default and will not be special
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsSingleVolumeChapter()
|
||||
{
|
||||
return MinNumber.Is(Parser.DefaultChapterNumber) && !IsSpecial;
|
||||
}
|
||||
}
|
||||
|
@ -29,4 +29,10 @@ public enum LibraryType
|
||||
/// </summary>
|
||||
[Description("Light Novel")]
|
||||
LightNovel = 4,
|
||||
/// <summary>
|
||||
/// Uses Comic regex for filename parsing, uses ComicVine type of Parsing. Will replace Comic type in future
|
||||
/// </summary>
|
||||
[Description("Comic (ComicVine)")]
|
||||
ComicVine = 5,
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,11 @@ public enum PersonRole
|
||||
/// <summary>
|
||||
/// The Translator
|
||||
/// </summary>
|
||||
Translator = 12
|
||||
|
||||
|
||||
Translator = 12,
|
||||
/// <summary>
|
||||
/// The publisher before another Publisher bought
|
||||
/// </summary>
|
||||
Imprint = 13,
|
||||
Team = 14,
|
||||
Location = 15
|
||||
}
|
||||
|
@ -71,6 +71,11 @@ public enum RelationKind
|
||||
/// Same story, could be translation, colorization... Different edition of the series
|
||||
/// </summary>
|
||||
[Description("Edition")]
|
||||
Edition = 13
|
||||
Edition = 13,
|
||||
/// <summary>
|
||||
/// The target series is an annual of the Series
|
||||
/// </summary>
|
||||
[Description("Annual")]
|
||||
Annual = 14
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,10 @@ public class MangaFile : IEntityDate
|
||||
{
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// The filename without extension
|
||||
/// </summary>
|
||||
public string FileName { get; set; }
|
||||
/// <summary>
|
||||
/// Absolute path to the archive file
|
||||
/// </summary>
|
||||
public required string FilePath { get; set; }
|
||||
|
@ -68,14 +68,16 @@ public class SeriesMetadata : IHasConcurrencyToken
|
||||
public bool ColoristLocked { get; set; }
|
||||
public bool EditorLocked { get; set; }
|
||||
public bool InkerLocked { get; set; }
|
||||
public bool ImprintLocked { get; set; }
|
||||
public bool LettererLocked { get; set; }
|
||||
public bool PencillerLocked { get; set; }
|
||||
public bool PublisherLocked { get; set; }
|
||||
public bool TranslatorLocked { get; set; }
|
||||
public bool TeamLocked { get; set; }
|
||||
public bool LocationLocked { get; set; }
|
||||
public bool CoverArtistLocked { get; set; }
|
||||
public bool ReleaseYearLocked { get; set; }
|
||||
|
||||
|
||||
// Relationship
|
||||
public Series Series { get; set; } = null!;
|
||||
public int SeriesId { get; set; }
|
||||
|
@ -64,6 +64,11 @@ public class Series : IEntityDate, IHasReadTimeEstimate
|
||||
/// <remarks><see cref="Services.Tasks.Scanner.Parser.Parser.NormalizePath"/> must be used before setting</remarks>
|
||||
public string? FolderPath { get; set; }
|
||||
/// <summary>
|
||||
/// Lowest path (that is under library root) that contains all files for the series.
|
||||
/// </summary>
|
||||
/// <remarks><see cref="Services.Tasks.Scanner.Parser.Parser.NormalizePath"/> must be used before setting</remarks>
|
||||
public string? LowestFolderPath { get; set; }
|
||||
/// <summary>
|
||||
/// Last time the folder was scanned
|
||||
/// </summary>
|
||||
public DateTime LastFolderScanned { get; set; }
|
||||
|
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using API.Entities.Interfaces;
|
||||
using API.Extensions;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
namespace API.Entities;
|
||||
|
||||
@ -13,6 +15,10 @@ public class Volume : IEntityDate, IHasReadTimeEstimate
|
||||
/// <remarks>For Books with Series_index, this will map to the Series Index.</remarks>
|
||||
public required string Name { get; set; }
|
||||
/// <summary>
|
||||
/// This is just the original Parsed volume number for lookups
|
||||
/// </summary>
|
||||
public string LookupName { get; set; }
|
||||
/// <summary>
|
||||
/// The minimum number in the Name field in Int form
|
||||
/// </summary>
|
||||
/// <remarks>Removed in v0.7.13.8, this was an int and we need the ability to have 0.5 volumes render on the UI</remarks>
|
||||
@ -55,4 +61,17 @@ public class Volume : IEntityDate, IHasReadTimeEstimate
|
||||
public Series Series { get; set; } = null!;
|
||||
public int SeriesId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Chapter Number. If the chapter is a range, returns that, formatted.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetNumberTitle()
|
||||
{
|
||||
if (MinNumber.Is(MaxNumber))
|
||||
{
|
||||
return $"{MinNumber}";
|
||||
}
|
||||
return $"{MinNumber}-{MaxNumber}";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ public static class ApplicationServiceExtensions
|
||||
services.AddScoped<ILibraryWatcher, LibraryWatcher>();
|
||||
services.AddScoped<ITachiyomiService, TachiyomiService>();
|
||||
services.AddScoped<ICollectionTagService, CollectionTagService>();
|
||||
services.AddScoped<ITagManagerService, TagManagerService>();
|
||||
|
||||
services.AddScoped<IFileSystem, FileSystem>();
|
||||
services.AddScoped<IDirectoryService, DirectoryService>();
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using API.Entities;
|
||||
using API.Helpers;
|
||||
@ -28,10 +29,11 @@ public static class ChapterListExtensions
|
||||
/// <returns></returns>
|
||||
public static Chapter? GetChapterByRange(this IEnumerable<Chapter> chapters, ParserInfo info)
|
||||
{
|
||||
var normalizedPath = Parser.NormalizePath(info.FullFilePath);
|
||||
var specialTreatment = info.IsSpecialInfo();
|
||||
return specialTreatment
|
||||
? chapters.FirstOrDefault(c => c.Range == info.Filename || (c.Files.Select(f => f.FilePath).Contains(info.FullFilePath)))
|
||||
: chapters.FirstOrDefault(c => c.Range == info.Chapters);
|
||||
return specialTreatment
|
||||
? chapters.FirstOrDefault(c => c.Range == Parser.RemoveExtensionIfSupported(info.Filename) || c.Files.Select(f => Parser.NormalizePath(f.FilePath)).Contains(normalizedPath))
|
||||
: chapters.FirstOrDefault(c => c.Range == info.Chapters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -41,6 +43,6 @@ public static class ChapterListExtensions
|
||||
/// <returns></returns>
|
||||
public static int MinimumReleaseYear(this IList<Chapter> chapters)
|
||||
{
|
||||
return chapters.Select(v => v.ReleaseDate.Year).Where(y => NumberHelper.IsValidYear(y)).DefaultIfEmpty().Min();
|
||||
return chapters.Select(v => v.ReleaseDate.Year).Where(NumberHelper.IsValidYear).DefaultIfEmpty().Min();
|
||||
}
|
||||
}
|
||||
|
26
API/Extensions/FloatExtensions.cs
Normal file
26
API/Extensions/FloatExtensions.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace API.Extensions;
|
||||
|
||||
public static class FloatExtensions
|
||||
{
|
||||
private const float Tolerance = 0.001f;
|
||||
|
||||
/// <summary>
|
||||
/// Used to compare 2 floats together
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
public static bool Is(this float a, float? b)
|
||||
{
|
||||
if (!b.HasValue) return false;
|
||||
return Math.Abs((float) (a - b)) < Tolerance;
|
||||
}
|
||||
|
||||
public static bool IsNot(this float a, float? b)
|
||||
{
|
||||
if (!b.HasValue) return false;
|
||||
return Math.Abs((float) (a - b)) > Tolerance;
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using API.Entities;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
@ -27,7 +28,9 @@ public static class ParserInfoListExtensions
|
||||
/// <returns></returns>
|
||||
public static bool HasInfo(this IList<ParserInfo> infos, Chapter chapter)
|
||||
{
|
||||
return chapter.IsSpecial ? infos.Any(v => v.Filename == chapter.Range)
|
||||
: infos.Any(v => v.Chapters == chapter.Range);
|
||||
var chapterFiles = chapter.Files.Select(x => Parser.NormalizePath(x.FilePath)).ToList();
|
||||
var infoFiles = infos.Select(x => Parser.NormalizePath(x.FullFilePath)).ToList();
|
||||
return infoFiles.Intersect(chapterFiles).Any();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -39,6 +39,31 @@ public static class IncludesExtensions
|
||||
return queryable.AsSplitQuery();
|
||||
}
|
||||
|
||||
public static IQueryable<Volume> Includes(this IQueryable<Volume> queryable,
|
||||
VolumeIncludes includes)
|
||||
{
|
||||
if (includes.HasFlag(VolumeIncludes.Chapters))
|
||||
{
|
||||
queryable = queryable.Include(vol => vol.Chapters);
|
||||
}
|
||||
|
||||
if (includes.HasFlag(VolumeIncludes.People))
|
||||
{
|
||||
queryable = queryable
|
||||
.Include(vol => vol.Chapters)
|
||||
.ThenInclude(c => c.People);
|
||||
}
|
||||
|
||||
if (includes.HasFlag(VolumeIncludes.Tags))
|
||||
{
|
||||
queryable = queryable
|
||||
.Include(vol => vol.Chapters)
|
||||
.ThenInclude(c => c.Tags);
|
||||
}
|
||||
|
||||
return queryable.AsSplitQuery();
|
||||
}
|
||||
|
||||
public static IQueryable<Series> Includes(this IQueryable<Series> query,
|
||||
SeriesIncludes includeFlags)
|
||||
{
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using API.Comparators;
|
||||
using API.Entities;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
@ -19,13 +17,19 @@ public static class SeriesExtensions
|
||||
public static string? GetCoverImage(this Series series)
|
||||
{
|
||||
var volumes = (series.Volumes ?? [])
|
||||
.OrderBy(v => v.MinNumber, ChapterSortComparer.Default)
|
||||
.OrderBy(v => v.MinNumber, ChapterSortComparerDefaultLast.Default)
|
||||
.ToList();
|
||||
var firstVolume = volumes.GetCoverImage(series.Format);
|
||||
if (firstVolume == null) return null;
|
||||
|
||||
// If first volume here is specials, move to the next as specials should almost always be last.
|
||||
if (firstVolume.MinNumber.Is(Parser.SpecialVolumeNumber) && volumes.Count > 1)
|
||||
{
|
||||
firstVolume = volumes[1];
|
||||
}
|
||||
|
||||
var chapters = firstVolume.Chapters
|
||||
.OrderBy(c => c.Number.AsDouble(), ChapterSortComparerZeroFirst.Default)
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.ToList();
|
||||
|
||||
if (chapters.Count > 1 && chapters.Exists(c => c.IsSpecial))
|
||||
@ -34,32 +38,42 @@ public static class SeriesExtensions
|
||||
}
|
||||
|
||||
// just volumes
|
||||
if (volumes.TrueForAll(v => $"{v.MinNumber}" != Parser.LooseLeafVolume))
|
||||
if (volumes.TrueForAll(v => v.MinNumber.IsNot(Parser.LooseLeafVolumeNumber)))
|
||||
{
|
||||
return firstVolume.CoverImage;
|
||||
}
|
||||
// If we have loose leaf chapters
|
||||
|
||||
// if loose leaf chapters AND volumes, just return first volume
|
||||
if (volumes.Count >= 1 && $"{volumes[0].MinNumber}" != Parser.LooseLeafVolume)
|
||||
if (volumes.Count >= 1 && volumes[0].MinNumber.IsNot(Parser.LooseLeafVolumeNumber))
|
||||
{
|
||||
var looseLeafChapters = volumes.Where(v => $"{v.MinNumber}" == Parser.LooseLeafVolume)
|
||||
.SelectMany(c => c.Chapters.Where(c => !c.IsSpecial))
|
||||
.OrderBy(c => c.Number.AsDouble(), ChapterSortComparerZeroFirst.Default)
|
||||
var looseLeafChapters = volumes.Where(v => v.MinNumber.Is(Parser.LooseLeafVolumeNumber))
|
||||
.SelectMany(c => c.Chapters.Where(c2 => !c2.IsSpecial))
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.ToList();
|
||||
if (looseLeafChapters.Count > 0 && (1.0f * volumes[0].MinNumber) > looseLeafChapters[0].Number.AsFloat())
|
||||
|
||||
if (looseLeafChapters.Count > 0 && volumes[0].MinNumber > looseLeafChapters[0].MinNumber)
|
||||
{
|
||||
var first = looseLeafChapters.Find(c => c.SortOrder.Is(1f));
|
||||
if (first != null) return first.CoverImage;
|
||||
return looseLeafChapters[0].CoverImage;
|
||||
}
|
||||
return firstVolume.CoverImage;
|
||||
}
|
||||
|
||||
var firstLooseLeafChapter = volumes
|
||||
.Where(v => $"{v.MinNumber}" == Parser.LooseLeafVolume)
|
||||
.SelectMany(v => v.Chapters)
|
||||
.OrderBy(c => c.Number.AsDouble(), ChapterSortComparerZeroFirst.Default)
|
||||
.FirstOrDefault(c => !c.IsSpecial);
|
||||
var chpts = volumes
|
||||
.First(v => v.MinNumber.Is(Parser.LooseLeafVolumeNumber))
|
||||
.Chapters
|
||||
.Where(c => !c.IsSpecial)
|
||||
.OrderBy(c => c.MinNumber, ChapterSortComparerDefaultLast.Default)
|
||||
.ToList();
|
||||
|
||||
return firstLooseLeafChapter?.CoverImage ?? firstVolume.CoverImage;
|
||||
var exactlyChapter1 = chpts.Find(c => c.MinNumber.Is(1f));
|
||||
if (exactlyChapter1 != null)
|
||||
{
|
||||
return exactlyChapter1.CoverImage;
|
||||
}
|
||||
|
||||
return chpts.FirstOrDefault()?.CoverImage ?? firstVolume.CoverImage;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using API.Comparators;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
@ -24,7 +25,7 @@ public static class VolumeListExtensions
|
||||
{
|
||||
if (volumes == null) throw new ArgumentException("Volumes cannot be null");
|
||||
|
||||
if (seriesFormat == MangaFormat.Epub || seriesFormat == MangaFormat.Pdf)
|
||||
if (seriesFormat is MangaFormat.Epub or MangaFormat.Pdf)
|
||||
{
|
||||
return volumes.MinBy(x => x.MinNumber);
|
||||
}
|
||||
@ -45,7 +46,7 @@ public static class VolumeListExtensions
|
||||
/// <returns></returns>
|
||||
public static bool HasAnyNonLooseLeafVolumes(this IEnumerable<Volume> volumes)
|
||||
{
|
||||
return volumes.Any(x => Math.Abs(x.MinNumber - Parser.DefaultChapterNumber) > 0.001f);
|
||||
return volumes.Any(v => v.MinNumber.IsNot(Parser.DefaultChapterNumber));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -55,7 +56,8 @@ public static class VolumeListExtensions
|
||||
/// <returns></returns>
|
||||
public static Volume? FirstNonLooseLeafOrDefault(this IEnumerable<Volume> volumes)
|
||||
{
|
||||
return volumes.OrderBy(x => x.MinNumber).FirstOrDefault(v => Math.Abs(v.MinNumber - Parser.DefaultChapterNumber) >= 0.001f);
|
||||
return volumes.OrderBy(x => x.MinNumber, ChapterSortComparerDefaultLast.Default)
|
||||
.FirstOrDefault(v => v.MinNumber.IsNot(Parser.DefaultChapterNumber));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -65,16 +67,26 @@ public static class VolumeListExtensions
|
||||
/// <returns></returns>
|
||||
public static Volume? GetLooseLeafVolumeOrDefault(this IEnumerable<Volume> volumes)
|
||||
{
|
||||
return volumes.FirstOrDefault(v => Math.Abs(v.MinNumber - Parser.DefaultChapterNumber) < 0.001f);
|
||||
return volumes.FirstOrDefault(v => v.MinNumber.Is(Parser.DefaultChapterNumber));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first (and only) special volume or null if none
|
||||
/// </summary>
|
||||
/// <param name="volumes"></param>
|
||||
/// <returns></returns>
|
||||
public static Volume? GetSpecialVolumeOrDefault(this IEnumerable<Volume> volumes)
|
||||
{
|
||||
return volumes.FirstOrDefault(v => v.MinNumber.Is(Parser.SpecialVolumeNumber));
|
||||
}
|
||||
|
||||
public static IEnumerable<VolumeDto> WhereNotLooseLeaf(this IEnumerable<VolumeDto> volumes)
|
||||
{
|
||||
return volumes.Where(v => Math.Abs(v.MinNumber - Parser.DefaultChapterNumber) >= 0.001f);
|
||||
return volumes.Where(v => v.MinNumber.Is(Parser.DefaultChapterNumber));
|
||||
}
|
||||
|
||||
public static IEnumerable<VolumeDto> WhereLooseLeaf(this IEnumerable<VolumeDto> volumes)
|
||||
{
|
||||
return volumes.Where(v => Math.Abs(v.MinNumber - Parser.DefaultChapterNumber) < 0.001f);
|
||||
return volumes.Where(v => v.MinNumber.Is(Parser.DefaultChapterNumber));
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ public class AutoMapperProfiles : Profile
|
||||
.ForMember(dest => dest.Series, opt => opt.MapFrom(src => src.Series));
|
||||
CreateMap<LibraryDto, Library>();
|
||||
CreateMap<Volume, VolumeDto>()
|
||||
.ForMember(dest => dest.Number, opt => opt.MapFrom(src => src.MinNumber));
|
||||
.ForMember(dest => dest.Number, opt => opt.MapFrom(src => (int) src.MinNumber));
|
||||
CreateMap<MangaFile, MangaFileDto>();
|
||||
CreateMap<Chapter, ChapterDto>();
|
||||
CreateMap<Series, SeriesDto>();
|
||||
@ -128,6 +128,14 @@ public class AutoMapperProfiles : Profile
|
||||
opt =>
|
||||
opt.MapFrom(
|
||||
src => src.People.Where(p => p.Role == PersonRole.Editor).OrderBy(p => p.NormalizedName)))
|
||||
.ForMember(dest => dest.Teams,
|
||||
opt =>
|
||||
opt.MapFrom(
|
||||
src => src.People.Where(p => p.Role == PersonRole.Team).OrderBy(p => p.NormalizedName)))
|
||||
.ForMember(dest => dest.Locations,
|
||||
opt =>
|
||||
opt.MapFrom(
|
||||
src => src.People.Where(p => p.Role == PersonRole.Location).OrderBy(p => p.NormalizedName)))
|
||||
.ForMember(dest => dest.Genres,
|
||||
opt =>
|
||||
opt.MapFrom(
|
||||
@ -154,6 +162,9 @@ public class AutoMapperProfiles : Profile
|
||||
.ForMember(dest => dest.Inkers,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Inker).OrderBy(p => p.NormalizedName)))
|
||||
.ForMember(dest => dest.Imprints,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Imprint).OrderBy(p => p.NormalizedName)))
|
||||
.ForMember(dest => dest.Letterers,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Letterer).OrderBy(p => p.NormalizedName)))
|
||||
@ -171,7 +182,14 @@ public class AutoMapperProfiles : Profile
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Character).OrderBy(p => p.NormalizedName)))
|
||||
.ForMember(dest => dest.Editors,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Editor).OrderBy(p => p.NormalizedName)));
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Editor).OrderBy(p => p.NormalizedName)))
|
||||
.ForMember(dest => dest.Teams,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Team).OrderBy(p => p.NormalizedName)))
|
||||
.ForMember(dest => dest.Locations,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Location).OrderBy(p => p.NormalizedName)))
|
||||
;
|
||||
|
||||
CreateMap<AppUser, UserDto>()
|
||||
.ForMember(dest => dest.AgeRestriction,
|
||||
@ -200,6 +218,8 @@ public class AutoMapperProfiles : Profile
|
||||
CreateMap<ReadingList, ReadingListDto>();
|
||||
CreateMap<ReadingListItem, ReadingListItemDto>();
|
||||
CreateMap<ScrobbleError, ScrobbleErrorDto>();
|
||||
CreateMap<ChapterDto, TachiyomiChapterDto>();
|
||||
CreateMap<Chapter, TachiyomiChapterDto>();
|
||||
|
||||
CreateMap<Series, SearchResultDto>()
|
||||
.ForMember(dest => dest.SeriesId,
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
@ -17,20 +18,25 @@ public class ChapterBuilder : IEntityBuilder<Chapter>
|
||||
{
|
||||
_chapter = new Chapter()
|
||||
{
|
||||
Range = string.IsNullOrEmpty(range) ? number : range,
|
||||
Range = string.IsNullOrEmpty(range) ? number : Parser.RemoveExtensionIfSupported(range),
|
||||
Title = string.IsNullOrEmpty(range) ? number : range,
|
||||
Number = Parser.MinNumberFromRange(number).ToString(CultureInfo.InvariantCulture),
|
||||
MinNumber = Parser.MinNumberFromRange(number),
|
||||
MaxNumber = Parser.MaxNumberFromRange(number),
|
||||
SortOrder = Parser.MinNumberFromRange(number),
|
||||
Files = new List<MangaFile>(),
|
||||
Pages = 1
|
||||
Pages = 1,
|
||||
CreatedUtc = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
public static ChapterBuilder FromParserInfo(ParserInfo info)
|
||||
{
|
||||
var specialTreatment = info.IsSpecialInfo();
|
||||
var specialTitle = specialTreatment ? info.Filename : info.Chapters;
|
||||
var specialTitle = specialTreatment ? Parser.RemoveExtensionIfSupported(info.Filename) : info.Chapters;
|
||||
var builder = new ChapterBuilder(Parser.DefaultChapter);
|
||||
return builder.WithNumber(specialTreatment ? Parser.DefaultChapter : Parser.MinNumberFromRange(info.Chapters) + string.Empty)
|
||||
|
||||
return builder.WithNumber(Parser.RemoveExtensionIfSupported(info.Chapters))
|
||||
.WithRange(specialTreatment ? info.Filename : info.Chapters)
|
||||
.WithTitle((specialTreatment && info.Format == MangaFormat.Epub)
|
||||
? info.Title
|
||||
@ -44,9 +50,18 @@ public class ChapterBuilder : IEntityBuilder<Chapter>
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChapterBuilder WithNumber(string number)
|
||||
|
||||
private ChapterBuilder WithNumber(string number)
|
||||
{
|
||||
_chapter.Number = number;
|
||||
_chapter.MinNumber = Parser.MinNumberFromRange(number);
|
||||
_chapter.MaxNumber = Parser.MaxNumberFromRange(number);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChapterBuilder WithSortOrder(float order)
|
||||
{
|
||||
_chapter.SortOrder = order;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -62,9 +77,9 @@ public class ChapterBuilder : IEntityBuilder<Chapter>
|
||||
return this;
|
||||
}
|
||||
|
||||
private ChapterBuilder WithRange(string range)
|
||||
public ChapterBuilder WithRange(string range)
|
||||
{
|
||||
_chapter.Range = range;
|
||||
_chapter.Range = Parser.RemoveExtensionIfSupported(range);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
namespace API.Helpers.Builders;
|
||||
|
||||
@ -19,6 +20,7 @@ public class MangaFileBuilder : IEntityBuilder<MangaFile>
|
||||
Pages = pages,
|
||||
LastModified = File.GetLastWriteTime(filePath),
|
||||
LastModifiedUtc = File.GetLastWriteTimeUtc(filePath),
|
||||
FileName = Parser.RemoveExtensionIfSupported(filePath)
|
||||
};
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user