mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-31 14:33:50 -04:00
Misc Bugfixes (#1123)
* Fixed a bug where ComicInfo Count can be a float and we threw a parse error. * Fixed a bug in download bookmarks which didn't properly create the filepaths for copying. Refactored into a service with a unit test. In Scanner, repull genres, people and tags between chunk saves to ensure no unique constraint issues. * Fixed a bug where card detail layout wouldn't refresh the library name on the card between pages * Fixed an issue where a check to scrolling page back to top was missing in manga reader * Fixed a bug where cleaning up collection tags without Series was missing after editing a Series. * Cleaned up the styles for cover chooser * Added Regex support for "Series 001 (Digital) (somethingwith1234)" and removed support for "A Compendium of Ghosts - 031 - The Third Story_ Part 12" due to complexity in parsing. * Fixed a miscommunication on how Tachiyomi needs the API MarkChaptersUntilAsRead implemented. Now 0 chapter volumes will be marked. * Removed unneeded DI
This commit is contained in:
parent
f74f356da2
commit
609fe82d6a
@ -6,6 +6,7 @@ namespace API.Tests.Parser
|
|||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("Gifting The Wonderful World With Blessings! - 3 Side Stories [yuNS][Unknown]", "Gifting The Wonderful World With Blessings!")]
|
[InlineData("Gifting The Wonderful World With Blessings! - 3 Side Stories [yuNS][Unknown]", "Gifting The Wonderful World With Blessings!")]
|
||||||
|
[InlineData("BBC Focus 00 The Science of Happiness 2nd Edition (2018)", "BBC Focus 00 The Science of Happiness 2nd Edition")]
|
||||||
public void ParseSeriesTest(string filename, string expected)
|
public void ParseSeriesTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
||||||
|
@ -159,7 +159,6 @@ namespace API.Tests.Parser
|
|||||||
[InlineData("The 100 Girlfriends Who Really, Really, Really, Really, Really Love You - Vol. 03 Ch. 023.5 - Volume 3 Extras.cbz", "The 100 Girlfriends Who Really, Really, Really, Really, Really Love You")]
|
[InlineData("The 100 Girlfriends Who Really, Really, Really, Really, Really Love You - Vol. 03 Ch. 023.5 - Volume 3 Extras.cbz", "The 100 Girlfriends Who Really, Really, Really, Really, Really Love You")]
|
||||||
[InlineData("Kimi no Koto ga Daidaidaidaidaisuki na 100-nin no Kanojo Chapter 1-10", "Kimi no Koto ga Daidaidaidaidaisuki na 100-nin no Kanojo")]
|
[InlineData("Kimi no Koto ga Daidaidaidaidaisuki na 100-nin no Kanojo Chapter 1-10", "Kimi no Koto ga Daidaidaidaidaisuki na 100-nin no Kanojo")]
|
||||||
[InlineData("The Duke of Death and His Black Maid - Ch. 177 - The Ball (3).cbz", "The Duke of Death and His Black Maid")]
|
[InlineData("The Duke of Death and His Black Maid - Ch. 177 - The Ball (3).cbz", "The Duke of Death and His Black Maid")]
|
||||||
[InlineData("A Compendium of Ghosts - 031 - The Third Story_ Part 12 (Digital) (Cobalt001)", "A Compendium of Ghosts")]
|
|
||||||
[InlineData("The Duke of Death and His Black Maid - Vol. 04 Ch. 054.5 - V4 Omake", "The Duke of Death and His Black Maid")]
|
[InlineData("The Duke of Death and His Black Maid - Vol. 04 Ch. 054.5 - V4 Omake", "The Duke of Death and His Black Maid")]
|
||||||
[InlineData("Vol. 04 Ch. 054.5", "")]
|
[InlineData("Vol. 04 Ch. 054.5", "")]
|
||||||
[InlineData("Great_Teacher_Onizuka_v16[TheSpectrum]", "Great Teacher Onizuka")]
|
[InlineData("Great_Teacher_Onizuka_v16[TheSpectrum]", "Great Teacher Onizuka")]
|
||||||
@ -168,6 +167,7 @@ namespace API.Tests.Parser
|
|||||||
[InlineData("Kaiju No. 8 036 (2021) (Digital)", "Kaiju No. 8")]
|
[InlineData("Kaiju No. 8 036 (2021) (Digital)", "Kaiju No. 8")]
|
||||||
[InlineData("Seraph of the End - Vampire Reign 093 (2020) (Digital) (LuCaZ).cbz", "Seraph of the End - Vampire Reign")]
|
[InlineData("Seraph of the End - Vampire Reign 093 (2020) (Digital) (LuCaZ).cbz", "Seraph of the End - Vampire Reign")]
|
||||||
[InlineData("Love Hina - Volume 01 [Scans].pdf", "Love Hina")]
|
[InlineData("Love Hina - Volume 01 [Scans].pdf", "Love Hina")]
|
||||||
|
[InlineData("It's Witching Time! 001 (Digital) (Anonymous1234)", "It's Witching Time!")]
|
||||||
public void ParseSeriesTest(string filename, string expected)
|
public void ParseSeriesTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
||||||
|
@ -11,6 +11,7 @@ using API.DTOs.Reader;
|
|||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
|
using API.SignalR;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -334,4 +335,65 @@ public class BookmarkServiceTests
|
|||||||
Assert.False(ds.FileSystem.FileInfo.FromFileName(Path.Join(BookmarkDirectory, "1/1/1/0001.jpg")).Exists);
|
Assert.False(ds.FileSystem.FileInfo.FromFileName(Path.Join(BookmarkDirectory, "1/1/1/0001.jpg")).Exists);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region GetBookmarkFilesById
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetBookmarkFilesById_ShouldMatchActualFiles()
|
||||||
|
{
|
||||||
|
var filesystem = CreateFileSystem();
|
||||||
|
filesystem.AddFile($"{CacheDirectory}1/0001.jpg", new MockFileData("123"));
|
||||||
|
|
||||||
|
// Delete all Series to reset state
|
||||||
|
await ResetDB();
|
||||||
|
|
||||||
|
_context.Series.Add(new Series()
|
||||||
|
{
|
||||||
|
Name = "Test",
|
||||||
|
Library = new Library() {
|
||||||
|
Name = "Test LIb",
|
||||||
|
Type = LibraryType.Manga,
|
||||||
|
},
|
||||||
|
Volumes = new List<Volume>()
|
||||||
|
{
|
||||||
|
new Volume()
|
||||||
|
{
|
||||||
|
Chapters = new List<Chapter>()
|
||||||
|
{
|
||||||
|
new Chapter()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_context.AppUser.Add(new AppUser()
|
||||||
|
{
|
||||||
|
UserName = "Joe"
|
||||||
|
});
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
|
||||||
|
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||||
|
var bookmarkService = new BookmarkService(Substitute.For<ILogger<BookmarkService>>(), _unitOfWork, ds);
|
||||||
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Bookmarks);
|
||||||
|
|
||||||
|
await bookmarkService.BookmarkPage(user, new BookmarkDto()
|
||||||
|
{
|
||||||
|
ChapterId = 1,
|
||||||
|
Page = 1,
|
||||||
|
SeriesId = 1,
|
||||||
|
VolumeId = 1
|
||||||
|
}, $"{CacheDirectory}1/0001.jpg");
|
||||||
|
|
||||||
|
var files = await bookmarkService.GetBookmarkFilesById(1, new[] {1});
|
||||||
|
var actualFiles = ds.GetFiles(BookmarkDirectory, searchOption: SearchOption.AllDirectories);
|
||||||
|
Assert.Equal(files.Select(API.Parser.Parser.NormalizePath).ToList(), actualFiles.Select(API.Parser.Parser.NormalizePath).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -1568,7 +1568,7 @@ public class ReaderServiceTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task MarkChaptersUntilAsRead_ShouldNotReadOnlyVolumesWithChapter0()
|
public async Task MarkChaptersUntilAsRead_ShouldMarkAsRead_OnlyVolumesWithChapter0()
|
||||||
{
|
{
|
||||||
_context.Series.Add(new Series()
|
_context.Series.Add(new Series()
|
||||||
{
|
{
|
||||||
@ -1604,7 +1604,7 @@ public class ReaderServiceTests
|
|||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
// Validate correct chapters have read status
|
// Validate correct chapters have read status
|
||||||
Assert.False(await _unitOfWork.AppUserProgressRepository.UserHasProgress(LibraryType.Manga, 1));
|
Assert.True(await _unitOfWork.AppUserProgressRepository.UserHasProgress(LibraryType.Manga, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -7,7 +7,6 @@ using API.Constants;
|
|||||||
using API.Data;
|
using API.Data;
|
||||||
using API.DTOs.Downloads;
|
using API.DTOs.Downloads;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
using API.SignalR;
|
using API.SignalR;
|
||||||
@ -15,7 +14,6 @@ using Kavita.Common;
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace API.Controllers
|
namespace API.Controllers
|
||||||
@ -30,10 +28,11 @@ namespace API.Controllers
|
|||||||
private readonly IEventHub _eventHub;
|
private readonly IEventHub _eventHub;
|
||||||
private readonly UserManager<AppUser> _userManager;
|
private readonly UserManager<AppUser> _userManager;
|
||||||
private readonly ILogger<DownloadController> _logger;
|
private readonly ILogger<DownloadController> _logger;
|
||||||
|
private readonly IBookmarkService _bookmarkService;
|
||||||
private const string DefaultContentType = "application/octet-stream";
|
private const string DefaultContentType = "application/octet-stream";
|
||||||
|
|
||||||
public DownloadController(IUnitOfWork unitOfWork, IArchiveService archiveService, IDirectoryService directoryService,
|
public DownloadController(IUnitOfWork unitOfWork, IArchiveService archiveService, IDirectoryService directoryService,
|
||||||
IDownloadService downloadService, IEventHub eventHub, UserManager<AppUser> userManager, ILogger<DownloadController> logger)
|
IDownloadService downloadService, IEventHub eventHub, UserManager<AppUser> userManager, ILogger<DownloadController> logger, IBookmarkService bookmarkService)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_archiveService = archiveService;
|
_archiveService = archiveService;
|
||||||
@ -42,6 +41,7 @@ namespace API.Controllers
|
|||||||
_eventHub = eventHub;
|
_eventHub = eventHub;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_bookmarkService = bookmarkService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("volume-size")]
|
[HttpGet("volume-size")]
|
||||||
@ -172,13 +172,7 @@ namespace API.Controllers
|
|||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(downloadBookmarkDto.Bookmarks.First().SeriesId);
|
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(downloadBookmarkDto.Bookmarks.First().SeriesId);
|
||||||
|
|
||||||
var bookmarkDirectory =
|
var files = await _bookmarkService.GetBookmarkFilesById(user.Id, downloadBookmarkDto.Bookmarks.Select(b => b.Id));
|
||||||
(await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BookmarkDirectory)).Value;
|
|
||||||
|
|
||||||
var files = (await _unitOfWork.UserRepository.GetAllBookmarksByIds(downloadBookmarkDto.Bookmarks
|
|
||||||
.Select(b => b.Id)
|
|
||||||
.ToList()))
|
|
||||||
.Select(b => Parser.Parser.NormalizePath(_directoryService.FileSystem.Path.Join(bookmarkDirectory, $"{b.ChapterId}_{b.FileName}")));
|
|
||||||
|
|
||||||
var filename = $"{series.Name} - Bookmarks.zip";
|
var filename = $"{series.Name} - Bookmarks.zip";
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||||
@ -187,6 +181,8 @@ namespace API.Controllers
|
|||||||
$"download_{user.Id}_{series.Id}_bookmarks");
|
$"download_{user.Id}_{series.Id}_bookmarks");
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||||
MessageFactory.DownloadProgressEvent(User.GetUsername(), Path.GetFileNameWithoutExtension(filename), 1F));
|
MessageFactory.DownloadProgressEvent(User.GetUsername(), Path.GetFileNameWithoutExtension(filename), 1F));
|
||||||
|
|
||||||
|
|
||||||
return File(fileBytes, DefaultContentType, filename);
|
return File(fileBytes, DefaultContentType, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ namespace API.Parser
|
|||||||
RegexTimeout),
|
RegexTimeout),
|
||||||
// Gokukoku no Brynhildr - c001-008 (v01) [TrinityBAKumA], Black Bullet - v4 c17 [batoto]
|
// Gokukoku no Brynhildr - c001-008 (v01) [TrinityBAKumA], Black Bullet - v4 c17 [batoto]
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)( - )(?:v|vo|c)\d",
|
@"(?<Series>.*)( - )(?:v|vo|c|chapters)\d",
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// Kedouin Makoto - Corpse Party Musume, Chapter 19 [Dametrans].zip
|
// Kedouin Makoto - Corpse Party Musume, Chapter 19 [Dametrans].zip
|
||||||
new Regex(
|
new Regex(
|
||||||
@ -153,16 +153,7 @@ namespace API.Parser
|
|||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// Historys Strongest Disciple Kenichi_v11_c90-98.zip, Killing Bites Vol. 0001 Ch. 0001 - Galactica Scanlations (gb)
|
// Historys Strongest Disciple Kenichi_v11_c90-98.zip, Killing Bites Vol. 0001 Ch. 0001 - Galactica Scanlations (gb)
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*) (\b|_|-)(v|ch\.?|c)\d+",
|
@"(?<Series>.*) (\b|_|-)(v|ch\.?|c|s)\d+",
|
||||||
MatchOptions, RegexTimeout),
|
|
||||||
//Ichinensei_ni_Nacchattara_v01_ch01_[Taruby]_v1.1.zip must be before [Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip
|
|
||||||
// due to duplicate version identifiers in file.
|
|
||||||
new Regex(
|
|
||||||
@"(?<Series>.*)(v|s)\d+(-\d+)?(_|\s)",
|
|
||||||
MatchOptions, RegexTimeout),
|
|
||||||
//[Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip
|
|
||||||
new Regex(
|
|
||||||
@"(?<Series>.*)(v|s)\d+(-\d+)?",
|
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// Hinowa ga CRUSH! 018 (2019) (Digital) (LuCaZ).cbz
|
// Hinowa ga CRUSH! 018 (2019) (Digital) (LuCaZ).cbz
|
||||||
new Regex(
|
new Regex(
|
||||||
@ -170,7 +161,7 @@ namespace API.Parser
|
|||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// Goblin Slayer - Brand New Day 006.5 (2019) (Digital) (danke-Empire)
|
// Goblin Slayer - Brand New Day 006.5 (2019) (Digital) (danke-Empire)
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*) (?<Chapter>\d+(?:.\d+|-\d+)?) \(\d{4}\)",
|
@"(?<Series>.*) (-)?(?<Chapter>\d+(?:.\d+|-\d+)?) \(\d{4}\)",
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// Noblesse - Episode 429 (74 Pages).7z
|
// Noblesse - Episode 429 (74 Pages).7z
|
||||||
new Regex(
|
new Regex(
|
||||||
@ -184,6 +175,23 @@ namespace API.Parser
|
|||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)(\s|_)\((c\s|ch\s|chapter\s)",
|
@"(?<Series>.*)(\s|_)\((c\s|ch\s|chapter\s)",
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
|
// Fullmetal Alchemist chapters 101-108
|
||||||
|
new Regex(
|
||||||
|
@"(?<Series>.+?)(\s|_|\-)+?chapters(\s|_|\-)+?\d+(\s|_|\-)+?",
|
||||||
|
MatchOptions, RegexTimeout),
|
||||||
|
// It's Witching Time! 001 (Digital) (Anonymous1234)
|
||||||
|
new Regex(
|
||||||
|
@"(?<Series>.+?)(\s|_|\-)+?\d+(\s|_|\-)\(",
|
||||||
|
MatchOptions, RegexTimeout),
|
||||||
|
//Ichinensei_ni_Nacchattara_v01_ch01_[Taruby]_v1.1.zip must be before [Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip
|
||||||
|
// due to duplicate version identifiers in file.
|
||||||
|
new Regex(
|
||||||
|
@"(?<Series>.*)(v|s)\d+(-\d+)?(_|\s)",
|
||||||
|
MatchOptions, RegexTimeout),
|
||||||
|
//[Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip
|
||||||
|
new Regex(
|
||||||
|
@"(?<Series>.*)(v|s)\d+(-\d+)?",
|
||||||
|
MatchOptions, RegexTimeout),
|
||||||
// Black Bullet (This is very loose, keep towards bottom)
|
// Black Bullet (This is very loose, keep towards bottom)
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)(_)(v|vo|c|volume)( |_)\d+",
|
@"(?<Series>.*)(_)(v|vo|c|volume)( |_)\d+",
|
||||||
|
@ -7,6 +7,7 @@ using API.Data;
|
|||||||
using API.DTOs.Reader;
|
using API.DTOs.Reader;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
using API.SignalR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace API.Services;
|
namespace API.Services;
|
||||||
@ -16,6 +17,7 @@ public interface IBookmarkService
|
|||||||
Task DeleteBookmarkFiles(IEnumerable<AppUserBookmark> bookmarks);
|
Task DeleteBookmarkFiles(IEnumerable<AppUserBookmark> bookmarks);
|
||||||
Task<bool> BookmarkPage(AppUser userWithBookmarks, BookmarkDto bookmarkDto, string imageToBookmark);
|
Task<bool> BookmarkPage(AppUser userWithBookmarks, BookmarkDto bookmarkDto, string imageToBookmark);
|
||||||
Task<bool> RemoveBookmarkPage(AppUser userWithBookmarks, BookmarkDto bookmarkDto);
|
Task<bool> RemoveBookmarkPage(AppUser userWithBookmarks, BookmarkDto bookmarkDto);
|
||||||
|
Task<IEnumerable<string>> GetBookmarkFilesById(int userId, IEnumerable<int> bookmarkIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BookmarkService : IBookmarkService
|
public class BookmarkService : IBookmarkService
|
||||||
@ -139,6 +141,17 @@ public class BookmarkService : IBookmarkService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<string>> GetBookmarkFilesById(int userId, IEnumerable<int> bookmarkIds)
|
||||||
|
{
|
||||||
|
var bookmarkDirectory =
|
||||||
|
(await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BookmarkDirectory)).Value;
|
||||||
|
|
||||||
|
var bookmarks = await _unitOfWork.UserRepository.GetAllBookmarksByIds(bookmarkIds.ToList());
|
||||||
|
return bookmarks
|
||||||
|
.Select(b => Parser.Parser.NormalizePath(_directoryService.FileSystem.Path.Join(bookmarkDirectory,
|
||||||
|
b.FileName)));
|
||||||
|
}
|
||||||
|
|
||||||
private static string BookmarkStem(int userId, int seriesId, int chapterId)
|
private static string BookmarkStem(int userId, int seriesId, int chapterId)
|
||||||
{
|
{
|
||||||
return Path.Join($"{userId}", $"{seriesId}", $"{chapterId}");
|
return Path.Join($"{userId}", $"{seriesId}", $"{chapterId}");
|
||||||
|
@ -394,7 +394,7 @@ public class ReaderService : IReaderService
|
|||||||
{
|
{
|
||||||
var chapters = volume.Chapters
|
var chapters = volume.Chapters
|
||||||
.OrderBy(c => float.Parse(c.Number))
|
.OrderBy(c => float.Parse(c.Number))
|
||||||
.Where(c => !c.IsSpecial && Parser.Parser.MaximumNumberFromRange(c.Range) <= chapterNumber && Parser.Parser.MaximumNumberFromRange(c.Range) > 0.0);
|
.Where(c => !c.IsSpecial && Parser.Parser.MaximumNumberFromRange(c.Range) <= chapterNumber);
|
||||||
MarkChaptersAsRead(user, volume.SeriesId, chapters);
|
MarkChaptersAsRead(user, volume.SeriesId, chapters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,8 @@ public class SeriesService : ISeriesService
|
|||||||
updateSeriesMetadataDto.SeriesMetadata.SeriesId), false);
|
updateSeriesMetadataDto.SeriesMetadata.SeriesId), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _unitOfWork.CollectionTagRepository.RemoveTagsWithoutSeries();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -439,6 +439,11 @@ public class ScannerService : IScannerService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
|
|
||||||
|
// Update the people, genres, and tags after committing as we might have inserted new ones.
|
||||||
|
allPeople = await _unitOfWork.PersonRepository.GetAllPeople();
|
||||||
|
allGenres = await _unitOfWork.GenreRepository.GetAllGenresAsync();
|
||||||
|
allTags = await _unitOfWork.TagRepository.GetAllTagsAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -463,7 +468,6 @@ public class ScannerService : IScannerService
|
|||||||
|
|
||||||
foreach (var series in librarySeries)
|
foreach (var series in librarySeries)
|
||||||
{
|
{
|
||||||
// TODO: Do I need this? Shouldn't this be NotificationProgress
|
|
||||||
// This is something more like, the series has finished updating in the backend. It may or may not have been modified.
|
// This is something more like, the series has finished updating in the backend. It may or may not have been modified.
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.ScanSeries, MessageFactory.ScanSeriesEvent(series.Id, series.Name));
|
await _eventHub.SendMessageAsync(MessageFactory.ScanSeries, MessageFactory.ScanSeriesEvent(series.Id, series.Name));
|
||||||
}
|
}
|
||||||
@ -882,9 +886,9 @@ public class ScannerService : IScannerService
|
|||||||
chapter.TotalCount = comicInfo.Count;
|
chapter.TotalCount = comicInfo.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(comicInfo.Number) && int.Parse(comicInfo.Number) > 0)
|
if (!string.IsNullOrEmpty(comicInfo.Number) && float.Parse(comicInfo.Number) > 0)
|
||||||
{
|
{
|
||||||
chapter.Count = int.Parse(comicInfo.Number);
|
chapter.Count = (int) Math.Floor(float.Parse(comicInfo.Number));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,7 +156,8 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.trackByIdentity = (index: number, item: any) => `${this.header}_${this.pagination?.currentPage}_${this.updateApplied}`;
|
this.trackByIdentity = (index: number, item: any) => `${this.header}_${this.pagination?.currentPage}_${this.updateApplied}_${item?.libraryId}`;
|
||||||
|
|
||||||
|
|
||||||
if (this.filterSettings === undefined) {
|
if (this.filterSettings === undefined) {
|
||||||
this.filterSettings = new FilterSettings();
|
this.filterSettings = new FilterSettings();
|
||||||
|
@ -6,15 +6,15 @@
|
|||||||
<div class="row g-0 mt-3 pb-3" *ngIf="mode === 'all'">
|
<div class="row g-0 mt-3 pb-3" *ngIf="mode === 'all'">
|
||||||
<div class="mx-auto">
|
<div class="mx-auto">
|
||||||
<div class="row g-0 mb-3">
|
<div class="row g-0 mb-3">
|
||||||
<i class="fa fa-file-upload mx-auto" style="font-size: 24px;" aria-hidden="true"></i>
|
<i class="fa fa-file-upload mx-auto" style="font-size: 24px; width: 20px;" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="mx-auto">
|
<div class="mx-auto" style="width: 350px;">
|
||||||
<a class="col" style="padding-right:0px" href="javascript:void(0)" (click)="mode = 'url'; setupEnterHandler()"><span class="phone-hidden">Enter a </span>Url</a>
|
<a class="col" style="padding-right:0px" href="javascript:void(0)" (click)="changeMode('url')"><span class="phone-hidden">Enter a </span>Url</a>
|
||||||
<span class="col" style="padding-right:0px">•</span>
|
<span class="col ps-1 pe-1">•</span>
|
||||||
<span class="col" style="padding-right:0px" href="javascript:void(0)">Drag and drop</span>
|
<span class="col" style="padding-right:0px" href="javascript:void(0)">Drag and drop</span>
|
||||||
<span class="col" style="padding-right:0px">•</span>
|
<span class="col ps-1 pe-1" style="padding-right:0px">•</span>
|
||||||
<a class="col" style="padding-right:0px" href="javascript:void(0)" (click)="openFileSelector()">Upload<span class="phone-hidden"> an image</span></a>
|
<a class="col" style="padding-right:0px" href="javascript:void(0)" (click)="openFileSelector()">Upload<span class="phone-hidden"> an image</span></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -24,17 +24,14 @@
|
|||||||
|
|
||||||
<ng-container *ngIf="mode === 'url'">
|
<ng-container *ngIf="mode === 'url'">
|
||||||
<div class="row g-0 mt-3 pb-3 ms-md-2 me-md-2">
|
<div class="row g-0 mt-3 pb-3 ms-md-2 me-md-2">
|
||||||
<div class="input-group col-md-10 me-md-2" style="width: 100%">
|
<div class="input-group col-auto me-md-2" style="width: 83%">
|
||||||
<!-- TOOD: Bootstrap migration: This should be replaced with just the label-->
|
<label class="input-group-text" for="load-image">Url</label>
|
||||||
<div class="input-group-prepend">
|
<input type="text" autofocus autocomplete="off" class="form-control" formControlName="coverImageUrl" placeholder="https://" id="load-image" class="form-control">
|
||||||
<label class="input-group-text form-label" for="load-image">Url</label>
|
|
||||||
</div>
|
|
||||||
<input type="text" autocomplete="off" class="form-control" formControlName="coverImageUrl" placeholder="https://" id="load-image" class="form-control">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" id="load-image-addon" (click)="loadImage(); mode='all';" [disabled]="form.get('coverImageUrl')?.value.length === 0">
|
<button class="btn btn-outline-secondary" type="button" id="load-image-addon" (click)="loadImage(); mode='all';" [disabled]="form.get('coverImageUrl')?.value.length === 0">
|
||||||
Load
|
Load
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="col btn btn-secondary" href="javascript:void(0)" (click)="mode = 'all'" aria-label="Back">
|
<button class="btn btn-secondary col-auto" href="javascript:void(0)" (click)="mode = 'all'" aria-label="Back">
|
||||||
<i class="fas fa-share" aria-hidden="true" style="transform: rotateY(180deg)"></i>
|
<i class="fas fa-share" aria-hidden="true" style="transform: rotateY(180deg)"></i>
|
||||||
<span class="phone-hidden">Back</span>
|
<span class="phone-hidden">Back</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -104,6 +104,14 @@ export class CoverImageChooserComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeMode(mode: 'url') {
|
||||||
|
this.mode = mode;
|
||||||
|
this.setupEnterHandler();
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -892,6 +892,12 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
if (!this.shouldRenderAsFitSplit()) {
|
if (!this.shouldRenderAsFitSplit()) {
|
||||||
this.setCanvasSize();
|
this.setCanvasSize();
|
||||||
this.ctx.drawImage(this.canvasImage, 0, 0);
|
this.ctx.drawImage(this.canvasImage, 0, 0);
|
||||||
|
|
||||||
|
// Reset scroll on non HEIGHT Fits
|
||||||
|
if (this.getFit() !== FITTING_OPTION.HEIGHT) {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user