mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-03 05:34:21 -04:00
More Fixes (#1993)
* Strip just isbn: from epub isbns and log when it's back (books) * Tweaked to allow invalid GTINs but only valid ISBN 10/13s will be saved to Kavita. * Fixed a bug with parsing series from a filename that is just a chapter range and no chapter/volume keywords. * Show the media issue count before you open accordion * Added a inpage filter for Media issues * Cleanup styles * Fixed up some code in epub isbn parsing when it's null * Encode filenames when downloading so that non english characters can be passed properly to UI. * Added support to parse ComicInfo's with Empty Tags. * Reset development settings. * Tweaked the code in generating reading lists to avoid extra work when not needed. * Fix comicvine's favicon * Fixed up a unit test * Tweaked the favicon code to ignore icons that have query parameters * More favicon work. Expanded ability to grab icons a bit. Added in ability to not keep requesting favicons when we failed to parse already. * Added a note for later * Fixed stats server url * Added more debugging * Fixed unit tests
This commit is contained in:
parent
cd8fca993b
commit
25703d6fe0
@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
|||||||
using API.Services;
|
using API.Services;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Order;
|
using BenchmarkDotNet.Order;
|
||||||
|
using EasyCaching.Core;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Png;
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
@ -31,7 +32,7 @@ public class ArchiveServiceBenchmark
|
|||||||
public ArchiveServiceBenchmark()
|
public ArchiveServiceBenchmark()
|
||||||
{
|
{
|
||||||
_directoryService = new DirectoryService(null, new FileSystem());
|
_directoryService = new DirectoryService(null, new FileSystem());
|
||||||
_imageService = new ImageService(null, _directoryService);
|
_imageService = new ImageService(null, _directoryService, Substitute.For<IEasyCachingProviderFactory>());
|
||||||
_archiveService = new ArchiveService(new NullLogger<ArchiveService>(), _directoryService, _imageService, Substitute.For<IMediaErrorService>());
|
_archiveService = new ArchiveService(new NullLogger<ArchiveService>(), _directoryService, _imageService, Substitute.For<IMediaErrorService>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,6 +197,7 @@ public class MangaParserTests
|
|||||||
[InlineData("Esquire 6권 2021년 10월호", "Esquire")]
|
[InlineData("Esquire 6권 2021년 10월호", "Esquire")]
|
||||||
[InlineData("Accel World: Vol 1", "Accel World")]
|
[InlineData("Accel World: Vol 1", "Accel World")]
|
||||||
[InlineData("Accel World Chapter 001 Volume 002", "Accel World")]
|
[InlineData("Accel World Chapter 001 Volume 002", "Accel World")]
|
||||||
|
[InlineData("Bleach 001-003", "Bleach")]
|
||||||
public void ParseSeriesTest(string filename, string expected)
|
public void ParseSeriesTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseSeries(filename));
|
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseSeries(filename));
|
||||||
@ -281,6 +282,7 @@ public class MangaParserTests
|
|||||||
[InlineData("Манга 2 Глава", "2")]
|
[InlineData("Манга 2 Глава", "2")]
|
||||||
[InlineData("Манга Том 1 2 Глава", "2")]
|
[InlineData("Манга Том 1 2 Глава", "2")]
|
||||||
[InlineData("Accel World Chapter 001 Volume 002", "1")]
|
[InlineData("Accel World Chapter 001 Volume 002", "1")]
|
||||||
|
[InlineData("Bleach 001-003", "1-3")]
|
||||||
public void ParseChaptersTest(string filename, string expected)
|
public void ParseChaptersTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseChapter(filename));
|
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseChapter(filename));
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using API.Archive;
|
using API.Archive;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
|
using EasyCaching.Core;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NetVips;
|
using NetVips;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
@ -28,7 +29,7 @@ public class ArchiveServiceTests
|
|||||||
{
|
{
|
||||||
_testOutputHelper = testOutputHelper;
|
_testOutputHelper = testOutputHelper;
|
||||||
_archiveService = new ArchiveService(_logger, _directoryService,
|
_archiveService = new ArchiveService(_logger, _directoryService,
|
||||||
new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService),
|
new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService, Substitute.For<IEasyCachingProviderFactory>()),
|
||||||
Substitute.For<IMediaErrorService>());
|
Substitute.For<IMediaErrorService>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +167,7 @@ public class ArchiveServiceTests
|
|||||||
public void GetCoverImage_Default_Test(string inputFile, string expectedOutputFile)
|
public void GetCoverImage_Default_Test(string inputFile, string expectedOutputFile)
|
||||||
{
|
{
|
||||||
var ds = Substitute.For<DirectoryService>(_directoryServiceLogger, new FileSystem());
|
var ds = Substitute.For<DirectoryService>(_directoryServiceLogger, new FileSystem());
|
||||||
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), ds);
|
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), ds, Substitute.For<IEasyCachingProviderFactory>());
|
||||||
var archiveService = Substitute.For<ArchiveService>(_logger, ds, imageService, Substitute.For<IMediaErrorService>());
|
var archiveService = Substitute.For<ArchiveService>(_logger, ds, imageService, Substitute.For<IMediaErrorService>());
|
||||||
|
|
||||||
var testDirectory = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages"));
|
var testDirectory = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages"));
|
||||||
@ -197,7 +198,7 @@ public class ArchiveServiceTests
|
|||||||
[InlineData("sorting.zip", "sorting.expected.png")]
|
[InlineData("sorting.zip", "sorting.expected.png")]
|
||||||
public void GetCoverImage_SharpCompress_Test(string inputFile, string expectedOutputFile)
|
public void GetCoverImage_SharpCompress_Test(string inputFile, string expectedOutputFile)
|
||||||
{
|
{
|
||||||
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService);
|
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService, Substitute.For<IEasyCachingProviderFactory>());
|
||||||
var archiveService = Substitute.For<ArchiveService>(_logger,
|
var archiveService = Substitute.For<ArchiveService>(_logger,
|
||||||
new DirectoryService(_directoryServiceLogger, new FileSystem()), imageService,
|
new DirectoryService(_directoryServiceLogger, new FileSystem()), imageService,
|
||||||
Substitute.For<IMediaErrorService>());
|
Substitute.For<IMediaErrorService>());
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Abstractions;
|
using System.IO.Abstractions;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
|
using EasyCaching.Core;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -16,7 +17,7 @@ public class BookServiceTests
|
|||||||
{
|
{
|
||||||
var directoryService = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new FileSystem());
|
var directoryService = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new FileSystem());
|
||||||
_bookService = new BookService(_logger, directoryService,
|
_bookService = new BookService(_logger, directoryService,
|
||||||
new ImageService(Substitute.For<ILogger<ImageService>>(), directoryService)
|
new ImageService(Substitute.For<ILogger<ImageService>>(), directoryService, Substitute.For<IEasyCachingProviderFactory>())
|
||||||
, Substitute.For<IMediaErrorService>());
|
, Substitute.For<IMediaErrorService>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
||||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||||
<PackageReference Include="Docnet.Core" Version="2.4.0-alpha.4" />
|
<PackageReference Include="Docnet.Core" Version="2.4.0-alpha.4" />
|
||||||
|
<PackageReference Include="EasyCaching.InMemory" Version="1.9.0" />
|
||||||
<PackageReference Include="ExCSS" Version="4.1.0" />
|
<PackageReference Include="ExCSS" Version="4.1.0" />
|
||||||
<PackageReference Include="Flurl" Version="3.0.7" />
|
<PackageReference Include="Flurl" Version="3.0.7" />
|
||||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||||
|
@ -117,7 +117,7 @@ public class DownloadController : BaseApiController
|
|||||||
private ActionResult GetFirstFileDownload(IEnumerable<MangaFile> files)
|
private ActionResult GetFirstFileDownload(IEnumerable<MangaFile> files)
|
||||||
{
|
{
|
||||||
var (zipFile, contentType, fileDownloadName) = _downloadService.GetFirstFileDownload(files);
|
var (zipFile, contentType, fileDownloadName) = _downloadService.GetFirstFileDownload(files);
|
||||||
return PhysicalFile(zipFile, contentType, fileDownloadName, true);
|
return PhysicalFile(zipFile, contentType, System.Web.HttpUtility.UrlEncode(fileDownloadName), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -163,7 +163,7 @@ public class DownloadController : BaseApiController
|
|||||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||||
MessageFactory.DownloadProgressEvent(User.GetUsername(),
|
MessageFactory.DownloadProgressEvent(User.GetUsername(),
|
||||||
Path.GetFileNameWithoutExtension(downloadName), 1F, "ended"));
|
Path.GetFileNameWithoutExtension(downloadName), 1F, "ended"));
|
||||||
return PhysicalFile(filePath, DefaultContentType, downloadName, true);
|
return PhysicalFile(filePath, DefaultContentType, System.Web.HttpUtility.UrlEncode(downloadName), true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -220,7 +220,7 @@ public class DownloadController : BaseApiController
|
|||||||
MessageFactory.DownloadProgressEvent(username, Path.GetFileNameWithoutExtension(filename), 1F));
|
MessageFactory.DownloadProgressEvent(username, Path.GetFileNameWithoutExtension(filename), 1F));
|
||||||
|
|
||||||
|
|
||||||
return PhysicalFile(filePath, DefaultContentType, filename, true);
|
return PhysicalFile(filePath, DefaultContentType, System.Web.HttpUtility.UrlEncode(filename), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,8 @@ public class ServerController : BaseApiController
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var zipPath = _archiveService.CreateZipForDownload(files, "logs");
|
var zipPath = _archiveService.CreateZipForDownload(files, "logs");
|
||||||
return PhysicalFile(zipPath, "application/zip", Path.GetFileName(zipPath), true);
|
return PhysicalFile(zipPath, "application/zip",
|
||||||
|
System.Web.HttpUtility.UrlEncode(Path.GetFileName(zipPath)), true);
|
||||||
}
|
}
|
||||||
catch (KavitaException ex)
|
catch (KavitaException ex)
|
||||||
{
|
{
|
||||||
|
@ -214,7 +214,14 @@ public class SettingsController : BaseApiController
|
|||||||
? $"{path}/"
|
? $"{path}/"
|
||||||
: path;
|
: path;
|
||||||
setting.Value = path;
|
setting.Value = path;
|
||||||
Configuration.BaseUrl = updateSettingsDto.BaseUrl;
|
try
|
||||||
|
{
|
||||||
|
Configuration.BaseUrl = updateSettingsDto.BaseUrl;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Could not set base url. Give this exception to majora2007");
|
||||||
|
}
|
||||||
_unitOfWork.SettingsRepository.Update(setting);
|
_unitOfWork.SettingsRepository.Update(setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,9 @@ public class UsersController : BaseApiController
|
|||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username);
|
||||||
_unitOfWork.UserRepository.Delete(user);
|
_unitOfWork.UserRepository.Delete(user);
|
||||||
|
|
||||||
|
//(TODO: After updating a role or removing a user, delete their token)
|
||||||
|
// await _userManager.RemoveAuthenticationTokenAsync(user, TokenOptions.DefaultProvider, RefreshTokenName);
|
||||||
|
|
||||||
if (await _unitOfWork.CommitAsync()) return Ok();
|
if (await _unitOfWork.CommitAsync()) return Ok();
|
||||||
|
|
||||||
return BadRequest("Could not delete the user.");
|
return BadRequest("Could not delete the user.");
|
||||||
|
@ -153,7 +153,7 @@ public class ComicInfo
|
|||||||
info.CoverArtist = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.CoverArtist);
|
info.CoverArtist = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.CoverArtist);
|
||||||
|
|
||||||
// We need to convert GTIN to ISBN
|
// We need to convert GTIN to ISBN
|
||||||
if (!string.IsNullOrEmpty(info.GTIN) && ArticleNumberHelper.IsValidGtin(info.GTIN))
|
if (!string.IsNullOrEmpty(info.GTIN))
|
||||||
{
|
{
|
||||||
// This is likely a valid ISBN
|
// This is likely a valid ISBN
|
||||||
if (info.GTIN[0] == '0')
|
if (info.GTIN[0] == '0')
|
||||||
@ -163,9 +163,10 @@ public class ComicInfo
|
|||||||
{
|
{
|
||||||
info.Isbn = potentialISBN;
|
info.Isbn = potentialISBN;
|
||||||
}
|
}
|
||||||
|
} else if (ArticleNumberHelper.IsValidIsbn10(info.GTIN) || ArticleNumberHelper.IsValidIsbn13(info.GTIN))
|
||||||
|
{
|
||||||
|
info.Isbn = info.GTIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,9 +22,7 @@ public static class ApplicationServiceExtensions
|
|||||||
services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
|
services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
|
||||||
|
|
||||||
services.AddScoped<IUnitOfWork, UnitOfWork>();
|
services.AddScoped<IUnitOfWork, UnitOfWork>();
|
||||||
services.AddScoped<IDirectoryService, DirectoryService>();
|
|
||||||
services.AddScoped<ITokenService, TokenService>();
|
services.AddScoped<ITokenService, TokenService>();
|
||||||
services.AddScoped<IFileSystem, FileSystem>();
|
|
||||||
services.AddScoped<IFileService, FileService>();
|
services.AddScoped<IFileService, FileService>();
|
||||||
services.AddScoped<ICacheHelper, CacheHelper>();
|
services.AddScoped<ICacheHelper, CacheHelper>();
|
||||||
|
|
||||||
@ -35,7 +33,6 @@ public static class ApplicationServiceExtensions
|
|||||||
services.AddScoped<IBackupService, BackupService>();
|
services.AddScoped<IBackupService, BackupService>();
|
||||||
services.AddScoped<ICleanupService, CleanupService>();
|
services.AddScoped<ICleanupService, CleanupService>();
|
||||||
services.AddScoped<IBookService, BookService>();
|
services.AddScoped<IBookService, BookService>();
|
||||||
services.AddScoped<IImageService, ImageService>();
|
|
||||||
services.AddScoped<IVersionUpdaterService, VersionUpdaterService>();
|
services.AddScoped<IVersionUpdaterService, VersionUpdaterService>();
|
||||||
services.AddScoped<IDownloadService, DownloadService>();
|
services.AddScoped<IDownloadService, DownloadService>();
|
||||||
services.AddScoped<IReaderService, ReaderService>();
|
services.AddScoped<IReaderService, ReaderService>();
|
||||||
@ -59,11 +56,20 @@ public static class ApplicationServiceExtensions
|
|||||||
services.AddScoped<ITachiyomiService, TachiyomiService>();
|
services.AddScoped<ITachiyomiService, TachiyomiService>();
|
||||||
services.AddScoped<ICollectionTagService, CollectionTagService>();
|
services.AddScoped<ICollectionTagService, CollectionTagService>();
|
||||||
|
|
||||||
services.AddScoped<IPresenceTracker, PresenceTracker>();
|
services.AddScoped<IFileSystem, FileSystem>();
|
||||||
|
services.AddScoped<IDirectoryService, DirectoryService>();
|
||||||
services.AddScoped<IEventHub, EventHub>();
|
services.AddScoped<IEventHub, EventHub>();
|
||||||
|
services.AddScoped<IPresenceTracker, PresenceTracker>();
|
||||||
|
|
||||||
|
services.AddScoped<IImageService, ImageService>();
|
||||||
|
|
||||||
services.AddSqLite(env);
|
services.AddSqLite(env);
|
||||||
services.AddSignalR(opt => opt.EnableDetailedErrors = true);
|
services.AddSignalR(opt => opt.EnableDetailedErrors = true);
|
||||||
|
|
||||||
|
services.AddEasyCaching(options =>
|
||||||
|
{
|
||||||
|
options.UseInMemory("favicon");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddSqLite(this IServiceCollection services, IHostEnvironment env)
|
private static void AddSqLite(this IServiceCollection services, IHostEnvironment env)
|
||||||
|
@ -4,6 +4,7 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Xml.Linq;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using API.Archive;
|
using API.Archive;
|
||||||
using API.Data.Metadata;
|
using API.Data.Metadata;
|
||||||
@ -372,10 +373,7 @@ public class ArchiveService : IArchiveService
|
|||||||
if (entry != null)
|
if (entry != null)
|
||||||
{
|
{
|
||||||
using var stream = entry.Open();
|
using var stream = entry.Open();
|
||||||
var serializer = new XmlSerializer(typeof(ComicInfo));
|
return Deserialize(stream);
|
||||||
var info = (ComicInfo?) serializer.Deserialize(stream);
|
|
||||||
ComicInfo.CleanComicInfo(info);
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -390,9 +388,7 @@ public class ArchiveService : IArchiveService
|
|||||||
if (entry != null)
|
if (entry != null)
|
||||||
{
|
{
|
||||||
using var stream = entry.OpenEntryStream();
|
using var stream = entry.OpenEntryStream();
|
||||||
var serializer = new XmlSerializer(typeof(ComicInfo));
|
var info = Deserialize(stream);
|
||||||
var info = (ComicInfo?) serializer.Deserialize(stream);
|
|
||||||
ComicInfo.CleanComicInfo(info);
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,6 +414,28 @@ public class ArchiveService : IArchiveService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Strips out empty tags before deserializing
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static ComicInfo? Deserialize(Stream stream)
|
||||||
|
{
|
||||||
|
var comicInfoXml = XDocument.Load(stream);
|
||||||
|
comicInfoXml.Descendants()
|
||||||
|
.Where(e => e.IsEmpty || string.IsNullOrWhiteSpace(e.Value))
|
||||||
|
.Remove();
|
||||||
|
|
||||||
|
var serializer = new XmlSerializer(typeof(ComicInfo));
|
||||||
|
using var reader = comicInfoXml.Root?.CreateReader();
|
||||||
|
if (reader == null) return null;
|
||||||
|
|
||||||
|
var info = (ComicInfo?) serializer.Deserialize(reader);
|
||||||
|
ComicInfo.CleanComicInfo(info);
|
||||||
|
return info;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void ExtractArchiveEntities(IEnumerable<IArchiveEntry> entries, string extractPath)
|
private void ExtractArchiveEntities(IEnumerable<IArchiveEntry> entries, string extractPath)
|
||||||
{
|
{
|
||||||
|
@ -437,8 +437,13 @@ public class BookService : IBookService
|
|||||||
|
|
||||||
foreach (var identifier in epubBook.Schema.Package.Metadata.Identifiers.Where(id => id.Scheme.Equals("ISBN")))
|
foreach (var identifier in epubBook.Schema.Package.Metadata.Identifiers.Where(id => id.Scheme.Equals("ISBN")))
|
||||||
{
|
{
|
||||||
var isbn = identifier.Identifier.Replace("urn:isbn:", string.Empty);
|
if (string.IsNullOrEmpty(identifier.Identifier)) continue;
|
||||||
if (!ArticleNumberHelper.IsValidIsbn10(isbn) && !ArticleNumberHelper.IsValidIsbn13(isbn)) continue;
|
var isbn = identifier.Identifier.Replace("urn:isbn:", string.Empty).Replace("isbn:", string.Empty);
|
||||||
|
if (!ArticleNumberHelper.IsValidIsbn10(isbn) && !ArticleNumberHelper.IsValidIsbn13(isbn))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("[BookService] {File} has invalid ISBN number", filePath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
info.Isbn = isbn;
|
info.Isbn = isbn;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
|
using EasyCaching.Core;
|
||||||
using Flurl;
|
using Flurl;
|
||||||
using Flurl.Http;
|
using Flurl.Http;
|
||||||
using HtmlAgilityPack;
|
using HtmlAgilityPack;
|
||||||
@ -63,11 +65,13 @@ public class ImageService : IImageService
|
|||||||
public const string Name = "BookmarkService";
|
public const string Name = "BookmarkService";
|
||||||
private readonly ILogger<ImageService> _logger;
|
private readonly ILogger<ImageService> _logger;
|
||||||
private readonly IDirectoryService _directoryService;
|
private readonly IDirectoryService _directoryService;
|
||||||
|
private readonly IEasyCachingProviderFactory _cacheFactory;
|
||||||
public const string ChapterCoverImageRegex = @"v\d+_c\d+";
|
public const string ChapterCoverImageRegex = @"v\d+_c\d+";
|
||||||
public const string SeriesCoverImageRegex = @"series\d+";
|
public const string SeriesCoverImageRegex = @"series\d+";
|
||||||
public const string CollectionTagCoverImageRegex = @"tag\d+";
|
public const string CollectionTagCoverImageRegex = @"tag\d+";
|
||||||
public const string ReadingListCoverImageRegex = @"readinglist\d+";
|
public const string ReadingListCoverImageRegex = @"readinglist\d+";
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Width of the Thumbnail generation
|
/// Width of the Thumbnail generation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -80,7 +84,8 @@ public class ImageService : IImageService
|
|||||||
private static readonly string[] ValidIconRelations = {
|
private static readonly string[] ValidIconRelations = {
|
||||||
"icon",
|
"icon",
|
||||||
"apple-touch-icon",
|
"apple-touch-icon",
|
||||||
"apple-touch-icon-precomposed"
|
"apple-touch-icon-precomposed",
|
||||||
|
"apple-touch-icon icon-precomposed" // ComicVine has it combined
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -91,10 +96,11 @@ public class ImageService : IImageService
|
|||||||
["https://app.plex.tv"] = "https://plex.tv"
|
["https://app.plex.tv"] = "https://plex.tv"
|
||||||
};
|
};
|
||||||
|
|
||||||
public ImageService(ILogger<ImageService> logger, IDirectoryService directoryService)
|
public ImageService(ILogger<ImageService> logger, IDirectoryService directoryService, IEasyCachingProviderFactory cacheFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_directoryService = directoryService;
|
_directoryService = directoryService;
|
||||||
|
_cacheFactory = cacheFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExtractImages(string? fileFilePath, string targetDirectory, int fileCount = 1)
|
public void ExtractImages(string? fileFilePath, string targetDirectory, int fileCount = 1)
|
||||||
@ -207,6 +213,15 @@ public class ImageService : IImageService
|
|||||||
var baseUrl = uri.Scheme + "://" + uri.Host;
|
var baseUrl = uri.Scheme + "://" + uri.Host;
|
||||||
|
|
||||||
|
|
||||||
|
var provider = _cacheFactory.GetCachingProvider("favicon");
|
||||||
|
var res = await provider.GetAsync<string>(baseUrl);
|
||||||
|
if (res.HasValue)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Kavita has already tried to fetch from {BaseUrl} and failed. Skipping duplicate check", baseUrl);
|
||||||
|
throw new KavitaException($"Kavita has already tried to fetch from {baseUrl} and failed. Skipping duplicate check");
|
||||||
|
}
|
||||||
|
|
||||||
|
await provider.SetAsync(baseUrl, string.Empty, TimeSpan.FromDays(10));
|
||||||
if (FaviconUrlMapper.TryGetValue(baseUrl, out var value))
|
if (FaviconUrlMapper.TryGetValue(baseUrl, out var value))
|
||||||
{
|
{
|
||||||
url = value;
|
url = value;
|
||||||
@ -220,7 +235,7 @@ public class ImageService : IImageService
|
|||||||
var pngLinks = htmlDocument.DocumentNode.Descendants("link")
|
var pngLinks = htmlDocument.DocumentNode.Descendants("link")
|
||||||
.Where(link => ValidIconRelations.Contains(link.GetAttributeValue("rel", string.Empty)))
|
.Where(link => ValidIconRelations.Contains(link.GetAttributeValue("rel", string.Empty)))
|
||||||
.Select(link => link.GetAttributeValue("href", string.Empty))
|
.Select(link => link.GetAttributeValue("href", string.Empty))
|
||||||
.Where(href => href.EndsWith(".png") || href.EndsWith(".PNG"))
|
.Where(href => href.Split("?")[0].EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (pngLinks == null)
|
if (pngLinks == null)
|
||||||
@ -234,7 +249,13 @@ public class ImageService : IImageService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var finalUrl = correctSizeLink;
|
var finalUrl = correctSizeLink;
|
||||||
if (!correctSizeLink.StartsWith(uri.Scheme))
|
|
||||||
|
// If starts with //, it's coming usually from an offsite cdn
|
||||||
|
if (correctSizeLink.StartsWith("//"))
|
||||||
|
{
|
||||||
|
finalUrl = Url.Combine("https:", correctSizeLink);
|
||||||
|
}
|
||||||
|
else if (!correctSizeLink.StartsWith(uri.Scheme))
|
||||||
{
|
{
|
||||||
finalUrl = Url.Combine(baseUrl, correctSizeLink);
|
finalUrl = Url.Combine(baseUrl, correctSizeLink);
|
||||||
}
|
}
|
||||||
@ -269,7 +290,7 @@ public class ImageService : IImageService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error downloading favicon.png for ${Domain}", domain);
|
_logger.LogError(ex, "Error downloading favicon.png for {Domain}", domain);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -496,12 +496,13 @@ public class ReadingListService : IReadingListService
|
|||||||
}
|
}
|
||||||
|
|
||||||
readingList.Items = items;
|
readingList.Items = items;
|
||||||
|
|
||||||
|
if (!_unitOfWork.HasChanges()) continue;
|
||||||
|
|
||||||
await CalculateReadingListAgeRating(readingList);
|
await CalculateReadingListAgeRating(readingList);
|
||||||
if (_unitOfWork.HasChanges())
|
await _unitOfWork.CommitAsync(); // TODO: See if we can avoid this extra commit by reworking bottom logic
|
||||||
{
|
await CalculateStartAndEndDates(await _unitOfWork.ReadingListRepository.GetReadingListByTitleAsync(arcPair.Item1,
|
||||||
await _unitOfWork.CommitAsync();
|
user.Id, ReadingListIncludes.Items | ReadingListIncludes.ItemChapter));
|
||||||
}
|
|
||||||
await CalculateStartAndEndDates(await _unitOfWork.ReadingListRepository.GetReadingListByTitleAsync(arcPair.Item1, user.Id, ReadingListIncludes.Items | ReadingListIncludes.ItemChapter));
|
|
||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,13 +321,9 @@ public static class Parser
|
|||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)( ?- ?)Ch\.\d+-?\d*",
|
@"(?<Series>.*)( ?- ?)Ch\.\d+-?\d*",
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// [BAA]_Darker_than_Black_Omake-1.zip
|
// [BAA]_Darker_than_Black_Omake-1, Bleach 001-002, Kodoja #001 (March 2016)
|
||||||
new Regex(
|
new Regex(
|
||||||
@"^(?!Vol)(?<Series>.*)(-)\d+-?\d*", // This catches a lot of stuff ^(?!Vol)(?<Series>.*)( |_)(\d+)
|
@"^(?!Vol)(?!Chapter)(?<Series>.+?)(-|_|\s|#)\d+(-\d+)?",
|
||||||
MatchOptions, RegexTimeout),
|
|
||||||
// Kodoja #001 (March 2016)
|
|
||||||
new Regex(
|
|
||||||
@"(?<Series>.*)(\s|_|-)#",
|
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// Baketeriya ch01-05.zip, Akiiro Bousou Biyori - 01.jpg, Beelzebub_172_RHS.zip, Cynthia the Mission 29.rar, A Compendium of Ghosts - 031 - The Third Story_ Part 12 (Digital) (Cobalt001)
|
// Baketeriya ch01-05.zip, Akiiro Bousou Biyori - 01.jpg, Beelzebub_172_RHS.zip, Cynthia the Mission 29.rar, A Compendium of Ghosts - 031 - The Third Story_ Part 12 (Digital) (Cobalt001)
|
||||||
new Regex(
|
new Regex(
|
||||||
|
@ -346,6 +346,8 @@ public class ProcessSeries : IProcessSeries
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region People
|
||||||
|
|
||||||
// Handle People
|
// Handle People
|
||||||
foreach (var chapter in chapters)
|
foreach (var chapter in chapters)
|
||||||
{
|
{
|
||||||
@ -490,6 +492,8 @@ public class ProcessSeries : IProcessSeries
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateVolumes(Series series, IList<ParserInfo> parsedInfos, bool forceUpdate = false)
|
public void UpdateVolumes(Series series, IList<ParserInfo> parsedInfos, bool forceUpdate = false)
|
||||||
|
@ -34,7 +34,7 @@ public class StatsService : IStatsService
|
|||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
private readonly DataContext _context;
|
private readonly DataContext _context;
|
||||||
private readonly IStatisticService _statisticService;
|
private readonly IStatisticService _statisticService;
|
||||||
private const string ApiUrl = "http://localhost:5003";
|
private const string ApiUrl = "https://stats.kavitareader.com";
|
||||||
|
|
||||||
public StatsService(ILogger<StatsService> logger, IUnitOfWork unitOfWork, DataContext context, IStatisticService statisticService)
|
public StatsService(ILogger<StatsService> logger, IUnitOfWork unitOfWork, DataContext context, IStatisticService statisticService)
|
||||||
{
|
{
|
||||||
|
@ -90,12 +90,12 @@ public class TokenService : ITokenService
|
|||||||
Token = await CreateToken(user),
|
Token = await CreateToken(user),
|
||||||
RefreshToken = await CreateRefreshToken(user)
|
RefreshToken = await CreateRefreshToken(user)
|
||||||
};
|
};
|
||||||
} catch (SecurityTokenExpiredException)
|
} catch (SecurityTokenExpiredException ex)
|
||||||
{
|
{
|
||||||
// Handle expired token
|
// Handle expired token
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Handle other exceptions
|
// Handle other exceptions
|
||||||
return null;
|
return null;
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
"TokenKey": "super secret unguessable key that is longer because we require it",
|
"TokenKey": "super secret unguessable key that is longer because we require it",
|
||||||
"Port": 5000,
|
"Port": 5000,
|
||||||
"IpAddresses": "",
|
"IpAddresses": "",
|
||||||
"BaseUrl": "/joe/"
|
"BaseUrl": "/"
|
||||||
}
|
}
|
@ -1,6 +1,16 @@
|
|||||||
<p>This table contains issues found during scan or reading of your media. This list is non-managed. You can clear it at any time and use Library (Force) Scan to perform analysis.</p>
|
<p>This table contains issues found during scan or reading of your media. This list is non-managed. You can clear it at any time and use Library (Force) Scan to perform analysis.</p>
|
||||||
|
|
||||||
<button class="btn btn-primary mb-2" (click)="clear()">Clear Alerts</button>
|
<form [formGroup]="formGroup">
|
||||||
|
<div class="row g-0 mb-3">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="filter" class="visually-hidden">Filter</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="filter" type="text" class="form-control" placeholder="Filter" formControlName="filter" />
|
||||||
|
<button class="btn btn-primary" type="button" (click)="clear()">Clear Alerts</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
<table class="table table-light table-hover table-sm table-hover">
|
<table class="table table-light table-hover table-sm table-hover">
|
||||||
<thead #header>
|
<thead #header>
|
||||||
<tr>
|
<tr>
|
||||||
@ -20,20 +30,22 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody #container>
|
<tbody #container>
|
||||||
<tr *ngIf="isLoading"><td colspan="4" style="text-align: center;"><app-loading [loading]="isLoading"></app-loading></td></tr>
|
<tr *ngIf="isLoading"><td colspan="4" style="text-align: center;"><app-loading [loading]="isLoading"></app-loading></td></tr>
|
||||||
<tr *ngIf="data.length === 0 && !isLoading"><td colspan="4" style="text-align: center;">No issues</td></tr>
|
<ng-container *ngIf="data | filter: filterList as filteredData">
|
||||||
<tr *ngFor="let item of data; index as i">
|
<tr *ngIf="filteredData.length === 0 && !isLoading"><td colspan="4" style="text-align: center;">No issues</td></tr>
|
||||||
<td>
|
<tr *ngFor="let item of filteredData; index as i">
|
||||||
{{item.extension}}
|
<td>
|
||||||
</td>
|
{{item.extension}}
|
||||||
<td>
|
</td>
|
||||||
{{item.filePath}}
|
<td>
|
||||||
</td>
|
{{item.filePath}}
|
||||||
<td>
|
</td>
|
||||||
{{item.comment}}
|
<td>
|
||||||
</td>
|
{{item.comment}}
|
||||||
<td>
|
</td>
|
||||||
{{item.details}}
|
<td>
|
||||||
</td>
|
{{item.details}}
|
||||||
</tr>
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
@ -1,9 +1,10 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit, QueryList, ViewChildren, inject } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit, Output, QueryList, ViewChildren, inject } from '@angular/core';
|
||||||
import { BehaviorSubject, Observable, Subject, combineLatest, filter, map, shareReplay, takeUntil } from 'rxjs';
|
import { BehaviorSubject, Observable, Subject, combineLatest, filter, map, shareReplay, takeUntil } from 'rxjs';
|
||||||
import { SortEvent, SortableHeader, compare } from 'src/app/_single-module/table/_directives/sortable-header.directive';
|
import { SortEvent, SortableHeader, compare } from 'src/app/_single-module/table/_directives/sortable-header.directive';
|
||||||
import { KavitaMediaError } from '../_models/media-error';
|
import { KavitaMediaError } from '../_models/media-error';
|
||||||
import { ServerService } from 'src/app/_services/server.service';
|
import { ServerService } from 'src/app/_services/server.service';
|
||||||
import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service';
|
import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service';
|
||||||
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-manage-alerts',
|
selector: 'app-manage-alerts',
|
||||||
@ -13,6 +14,7 @@ import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service
|
|||||||
})
|
})
|
||||||
export class ManageAlertsComponent implements OnInit {
|
export class ManageAlertsComponent implements OnInit {
|
||||||
|
|
||||||
|
@Output() alertCount = new EventEmitter<number>();
|
||||||
@ViewChildren(SortableHeader<KavitaMediaError>) headers!: QueryList<SortableHeader<KavitaMediaError>>;
|
@ViewChildren(SortableHeader<KavitaMediaError>) headers!: QueryList<SortableHeader<KavitaMediaError>>;
|
||||||
private readonly serverService = inject(ServerService);
|
private readonly serverService = inject(ServerService);
|
||||||
private readonly messageHub = inject(MessageHubService);
|
private readonly messageHub = inject(MessageHubService);
|
||||||
@ -25,6 +27,9 @@ export class ManageAlertsComponent implements OnInit {
|
|||||||
|
|
||||||
data: Array<KavitaMediaError> = [];
|
data: Array<KavitaMediaError> = [];
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
formGroup = new FormGroup({
|
||||||
|
filter: new FormControl('', [])
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
@ -63,8 +68,7 @@ export class ManageAlertsComponent implements OnInit {
|
|||||||
this.serverService.getMediaErrors().subscribe(d => {
|
this.serverService.getMediaErrors().subscribe(d => {
|
||||||
this.data = d;
|
this.data = d;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
console.log(this.data)
|
this.alertCount.emit(d.length);
|
||||||
console.log(this.isLoading)
|
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -72,4 +76,9 @@ export class ManageAlertsComponent implements OnInit {
|
|||||||
clear() {
|
clear() {
|
||||||
this.serverService.clearMediaAlerts().subscribe(_ => this.loadData());
|
this.serverService.clearMediaAlerts().subscribe(_ => this.loadData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filterList = (listItem: KavitaMediaError) => {
|
||||||
|
const query = (this.formGroup.get('filter')?.value || '').toLowerCase();
|
||||||
|
return listItem.comment.toLowerCase().indexOf(query) >= 0 || listItem.filePath.toLowerCase().indexOf(query) >= 0 || listItem.details.indexOf(query) >= 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,13 +37,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ngb-accordion #a="ngbAccordion">
|
<ngb-accordion #a="ngbAccordion" [destroyOnHide]="false">
|
||||||
<ngb-panel>
|
<ngb-panel>
|
||||||
<ng-template ngbPanelTitle>
|
<ng-template ngbPanelTitle>
|
||||||
Media Issues
|
Media Issues <span class="ms-1" *ngIf="alertCount > 0">({{alertCount}})</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ngbPanelContent>
|
<ng-template ngbPanelContent>
|
||||||
<app-manage-alerts></app-manage-alerts>
|
<app-manage-alerts (alertCount)="alertCount = $event"></app-manage-alerts>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-panel>
|
</ngb-panel>
|
||||||
</ngb-accordion>
|
</ngb-accordion>
|
||||||
|
@ -18,6 +18,8 @@ export class ManageMediaSettingsComponent implements OnInit {
|
|||||||
serverSettings!: ServerSettings;
|
serverSettings!: ServerSettings;
|
||||||
settingsForm: FormGroup = new FormGroup({});
|
settingsForm: FormGroup = new FormGroup({});
|
||||||
|
|
||||||
|
alertCount: number = 0;
|
||||||
|
|
||||||
get EncodeFormats() { return EncodeFormats; }
|
get EncodeFormats() { return EncodeFormats; }
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService, private toastr: ToastrService, private modalService: NgbModal, ) { }
|
constructor(private settingsService: SettingsService, private toastr: ToastrService, private modalService: NgbModal, ) { }
|
||||||
|
@ -55,6 +55,7 @@ export class DownloadService {
|
|||||||
private downloadsSource: BehaviorSubject<DownloadEvent[]> = new BehaviorSubject<DownloadEvent[]>([]);
|
private downloadsSource: BehaviorSubject<DownloadEvent[]> = new BehaviorSubject<DownloadEvent[]>([]);
|
||||||
public activeDownloads$ = this.downloadsSource.asObservable();
|
public activeDownloads$ = this.downloadsSource.asObservable();
|
||||||
|
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient, private confirmService: ConfirmService,
|
constructor(private httpClient: HttpClient, private confirmService: ConfirmService,
|
||||||
@Inject(SAVER) private save: Saver, private accountService: AccountService) { }
|
@Inject(SAVER) private save: Saver, private accountService: AccountService) { }
|
||||||
|
|
||||||
@ -159,7 +160,7 @@ export class DownloadService {
|
|||||||
).pipe(
|
).pipe(
|
||||||
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
||||||
download((blob, filename) => {
|
download((blob, filename) => {
|
||||||
this.save(blob, filename);
|
this.save(blob, decodeURIComponent(filename));
|
||||||
}),
|
}),
|
||||||
tap((d) => this.updateDownloadState(d, downloadType, subtitle)),
|
tap((d) => this.updateDownloadState(d, downloadType, subtitle)),
|
||||||
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
||||||
@ -174,7 +175,7 @@ export class DownloadService {
|
|||||||
).pipe(
|
).pipe(
|
||||||
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
||||||
download((blob, filename) => {
|
download((blob, filename) => {
|
||||||
this.save(blob, filename);
|
this.save(blob, decodeURIComponent(filename));
|
||||||
}),
|
}),
|
||||||
tap((d) => this.updateDownloadState(d, downloadType, subtitle)),
|
tap((d) => this.updateDownloadState(d, downloadType, subtitle)),
|
||||||
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
||||||
@ -213,7 +214,7 @@ export class DownloadService {
|
|||||||
).pipe(
|
).pipe(
|
||||||
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
||||||
download((blob, filename) => {
|
download((blob, filename) => {
|
||||||
this.save(blob, filename);
|
this.save(blob, decodeURIComponent(filename));
|
||||||
}),
|
}),
|
||||||
tap((d) => this.updateDownloadState(d, downloadType, subtitle)),
|
tap((d) => this.updateDownloadState(d, downloadType, subtitle)),
|
||||||
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
||||||
@ -228,7 +229,7 @@ export class DownloadService {
|
|||||||
).pipe(
|
).pipe(
|
||||||
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
||||||
download((blob, filename) => {
|
download((blob, filename) => {
|
||||||
this.save(blob, filename);
|
this.save(blob, decodeURIComponent(filename));
|
||||||
}),
|
}),
|
||||||
tap((d) => this.updateDownloadState(d, downloadType, subtitle)),
|
tap((d) => this.updateDownloadState(d, downloadType, subtitle)),
|
||||||
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
||||||
@ -248,7 +249,7 @@ export class DownloadService {
|
|||||||
).pipe(
|
).pipe(
|
||||||
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
||||||
download((blob, filename) => {
|
download((blob, filename) => {
|
||||||
this.save(blob, filename);
|
this.save(blob, decodeURIComponent(filename));
|
||||||
}),
|
}),
|
||||||
tap((d) => this.updateDownloadState(d, downloadType, subtitle)),
|
tap((d) => this.updateDownloadState(d, downloadType, subtitle)),
|
||||||
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
||||||
|
@ -27,6 +27,9 @@ if [ ! -f "/kavita/config/appsettings.json" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "App setting permissions"
|
||||||
|
echo ls -l "/kavita/config/appsettings.json"
|
||||||
|
|
||||||
chmod +x Kavita
|
chmod +x Kavita
|
||||||
|
|
||||||
./Kavita
|
./Kavita
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.2.6"
|
"version": "0.7.2.7"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user