diff --git a/API.Benchmark/ArchiveServiceBenchmark.cs b/API.Benchmark/ArchiveServiceBenchmark.cs index 0d13623c2..9ef8e237b 100644 --- a/API.Benchmark/ArchiveServiceBenchmark.cs +++ b/API.Benchmark/ArchiveServiceBenchmark.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging.Abstractions; using API.Services; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Order; +using EasyCaching.Core; using NSubstitute; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; @@ -31,7 +32,7 @@ public class ArchiveServiceBenchmark public ArchiveServiceBenchmark() { _directoryService = new DirectoryService(null, new FileSystem()); - _imageService = new ImageService(null, _directoryService); + _imageService = new ImageService(null, _directoryService, Substitute.For()); _archiveService = new ArchiveService(new NullLogger(), _directoryService, _imageService, Substitute.For()); } diff --git a/API.Tests/Parser/MangaParserTests.cs b/API.Tests/Parser/MangaParserTests.cs index 208ace3bc..5e3e1cef8 100644 --- a/API.Tests/Parser/MangaParserTests.cs +++ b/API.Tests/Parser/MangaParserTests.cs @@ -197,6 +197,7 @@ public class MangaParserTests [InlineData("Esquire 6권 2021년 10월호", "Esquire")] [InlineData("Accel World: Vol 1", "Accel World")] [InlineData("Accel World Chapter 001 Volume 002", "Accel World")] + [InlineData("Bleach 001-003", "Bleach")] public void ParseSeriesTest(string filename, string expected) { Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseSeries(filename)); @@ -281,6 +282,7 @@ public class MangaParserTests [InlineData("Манга 2 Глава", "2")] [InlineData("Манга Том 1 2 Глава", "2")] [InlineData("Accel World Chapter 001 Volume 002", "1")] + [InlineData("Bleach 001-003", "1-3")] public void ParseChaptersTest(string filename, string expected) { Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseChapter(filename)); diff --git a/API.Tests/Services/ArchiveServiceTests.cs b/API.Tests/Services/ArchiveServiceTests.cs index 24041198e..ae57dfe60 100644 --- a/API.Tests/Services/ArchiveServiceTests.cs +++ b/API.Tests/Services/ArchiveServiceTests.cs @@ -7,6 +7,7 @@ using System.Linq; using API.Archive; using API.Entities.Enums; using API.Services; +using EasyCaching.Core; using Microsoft.Extensions.Logging; using NetVips; using NSubstitute; @@ -28,7 +29,7 @@ public class ArchiveServiceTests { _testOutputHelper = testOutputHelper; _archiveService = new ArchiveService(_logger, _directoryService, - new ImageService(Substitute.For>(), _directoryService), + new ImageService(Substitute.For>(), _directoryService, Substitute.For()), Substitute.For()); } @@ -166,7 +167,7 @@ public class ArchiveServiceTests public void GetCoverImage_Default_Test(string inputFile, string expectedOutputFile) { var ds = Substitute.For(_directoryServiceLogger, new FileSystem()); - var imageService = new ImageService(Substitute.For>(), ds); + var imageService = new ImageService(Substitute.For>(), ds, Substitute.For()); var archiveService = Substitute.For(_logger, ds, imageService, Substitute.For()); 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")] public void GetCoverImage_SharpCompress_Test(string inputFile, string expectedOutputFile) { - var imageService = new ImageService(Substitute.For>(), _directoryService); + var imageService = new ImageService(Substitute.For>(), _directoryService, Substitute.For()); var archiveService = Substitute.For(_logger, new DirectoryService(_directoryServiceLogger, new FileSystem()), imageService, Substitute.For()); diff --git a/API.Tests/Services/BookServiceTests.cs b/API.Tests/Services/BookServiceTests.cs index f810b9e22..e4647524e 100644 --- a/API.Tests/Services/BookServiceTests.cs +++ b/API.Tests/Services/BookServiceTests.cs @@ -1,6 +1,7 @@ using System.IO; using System.IO.Abstractions; using API.Services; +using EasyCaching.Core; using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; @@ -16,7 +17,7 @@ public class BookServiceTests { var directoryService = new DirectoryService(Substitute.For>(), new FileSystem()); _bookService = new BookService(_logger, directoryService, - new ImageService(Substitute.For>(), directoryService) + new ImageService(Substitute.For>(), directoryService, Substitute.For()) , Substitute.For()); } diff --git a/API/API.csproj b/API/API.csproj index f13badffe..0decaef67 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -56,6 +56,7 @@ + diff --git a/API/Controllers/DownloadController.cs b/API/Controllers/DownloadController.cs index be1db4969..754ed7503 100644 --- a/API/Controllers/DownloadController.cs +++ b/API/Controllers/DownloadController.cs @@ -117,7 +117,7 @@ public class DownloadController : BaseApiController private ActionResult GetFirstFileDownload(IEnumerable files) { var (zipFile, contentType, fileDownloadName) = _downloadService.GetFirstFileDownload(files); - return PhysicalFile(zipFile, contentType, fileDownloadName, true); + return PhysicalFile(zipFile, contentType, System.Web.HttpUtility.UrlEncode(fileDownloadName), true); } /// @@ -163,7 +163,7 @@ public class DownloadController : BaseApiController await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.DownloadProgressEvent(User.GetUsername(), Path.GetFileNameWithoutExtension(downloadName), 1F, "ended")); - return PhysicalFile(filePath, DefaultContentType, downloadName, true); + return PhysicalFile(filePath, DefaultContentType, System.Web.HttpUtility.UrlEncode(downloadName), true); } catch (Exception ex) { @@ -220,7 +220,7 @@ public class DownloadController : BaseApiController MessageFactory.DownloadProgressEvent(username, Path.GetFileNameWithoutExtension(filename), 1F)); - return PhysicalFile(filePath, DefaultContentType, filename, true); + return PhysicalFile(filePath, DefaultContentType, System.Web.HttpUtility.UrlEncode(filename), true); } } diff --git a/API/Controllers/ServerController.cs b/API/Controllers/ServerController.cs index c799944b3..8cc293bee 100644 --- a/API/Controllers/ServerController.cs +++ b/API/Controllers/ServerController.cs @@ -150,7 +150,8 @@ public class ServerController : BaseApiController try { 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) { diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index 29393f6ce..ef73fdda6 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -214,7 +214,14 @@ public class SettingsController : BaseApiController ? $"{path}/" : 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); } diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index 1a6373ba2..d31f64104 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -33,6 +33,9 @@ public class UsersController : BaseApiController var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username); _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(); return BadRequest("Could not delete the user."); diff --git a/API/Data/Metadata/ComicInfo.cs b/API/Data/Metadata/ComicInfo.cs index c584b7ef0..acfdca1eb 100644 --- a/API/Data/Metadata/ComicInfo.cs +++ b/API/Data/Metadata/ComicInfo.cs @@ -153,7 +153,7 @@ public class ComicInfo info.CoverArtist = Services.Tasks.Scanner.Parser.Parser.CleanAuthor(info.CoverArtist); // 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 if (info.GTIN[0] == '0') @@ -163,9 +163,10 @@ public class ComicInfo { info.Isbn = potentialISBN; } - + } else if (ArticleNumberHelper.IsValidIsbn10(info.GTIN) || ArticleNumberHelper.IsValidIsbn13(info.GTIN)) + { + info.Isbn = info.GTIN; } - } } diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs index ba2b201ae..ef3a65710 100644 --- a/API/Extensions/ApplicationServiceExtensions.cs +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -22,9 +22,7 @@ public static class ApplicationServiceExtensions services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly); services.AddScoped(); - services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -35,7 +33,6 @@ public static class ApplicationServiceExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -59,11 +56,20 @@ public static class ApplicationServiceExtensions services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); services.AddSqLite(env); services.AddSignalR(opt => opt.EnableDetailedErrors = true); + + services.AddEasyCaching(options => + { + options.UseInMemory("favicon"); + }); } private static void AddSqLite(this IServiceCollection services, IHostEnvironment env) diff --git a/API/Services/ArchiveService.cs b/API/Services/ArchiveService.cs index e05102ad3..8a2278252 100644 --- a/API/Services/ArchiveService.cs +++ b/API/Services/ArchiveService.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; +using System.Xml.Linq; using System.Xml.Serialization; using API.Archive; using API.Data.Metadata; @@ -372,10 +373,7 @@ public class ArchiveService : IArchiveService if (entry != null) { using var stream = entry.Open(); - var serializer = new XmlSerializer(typeof(ComicInfo)); - var info = (ComicInfo?) serializer.Deserialize(stream); - ComicInfo.CleanComicInfo(info); - return info; + return Deserialize(stream); } break; @@ -390,9 +388,7 @@ public class ArchiveService : IArchiveService if (entry != null) { using var stream = entry.OpenEntryStream(); - var serializer = new XmlSerializer(typeof(ComicInfo)); - var info = (ComicInfo?) serializer.Deserialize(stream); - ComicInfo.CleanComicInfo(info); + var info = Deserialize(stream); return info; } @@ -418,6 +414,28 @@ public class ArchiveService : IArchiveService return null; } + /// + /// Strips out empty tags before deserializing + /// + /// + /// + 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 entries, string extractPath) { diff --git a/API/Services/BookService.cs b/API/Services/BookService.cs index 0ba1f5dd3..2c13892d8 100644 --- a/API/Services/BookService.cs +++ b/API/Services/BookService.cs @@ -437,8 +437,13 @@ public class BookService : IBookService 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 (!ArticleNumberHelper.IsValidIsbn10(isbn) && !ArticleNumberHelper.IsValidIsbn13(isbn)) continue; + if (string.IsNullOrEmpty(identifier.Identifier)) 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; break; } diff --git a/API/Services/ImageService.cs b/API/Services/ImageService.cs index b153cd6c7..2230850c3 100644 --- a/API/Services/ImageService.cs +++ b/API/Services/ImageService.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using API.Entities.Enums; using API.Extensions; +using EasyCaching.Core; using Flurl; using Flurl.Http; using HtmlAgilityPack; @@ -63,11 +65,13 @@ public class ImageService : IImageService public const string Name = "BookmarkService"; private readonly ILogger _logger; private readonly IDirectoryService _directoryService; + private readonly IEasyCachingProviderFactory _cacheFactory; public const string ChapterCoverImageRegex = @"v\d+_c\d+"; public const string SeriesCoverImageRegex = @"series\d+"; public const string CollectionTagCoverImageRegex = @"tag\d+"; public const string ReadingListCoverImageRegex = @"readinglist\d+"; + /// /// Width of the Thumbnail generation /// @@ -80,7 +84,8 @@ public class ImageService : IImageService private static readonly string[] ValidIconRelations = { "icon", "apple-touch-icon", - "apple-touch-icon-precomposed" + "apple-touch-icon-precomposed", + "apple-touch-icon icon-precomposed" // ComicVine has it combined }; /// @@ -91,10 +96,11 @@ public class ImageService : IImageService ["https://app.plex.tv"] = "https://plex.tv" }; - public ImageService(ILogger logger, IDirectoryService directoryService) + public ImageService(ILogger logger, IDirectoryService directoryService, IEasyCachingProviderFactory cacheFactory) { _logger = logger; _directoryService = directoryService; + _cacheFactory = cacheFactory; } 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 provider = _cacheFactory.GetCachingProvider("favicon"); + var res = await provider.GetAsync(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)) { url = value; @@ -220,7 +235,7 @@ public class ImageService : IImageService var pngLinks = htmlDocument.DocumentNode.Descendants("link") .Where(link => ValidIconRelations.Contains(link.GetAttributeValue("rel", 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(); if (pngLinks == null) @@ -234,7 +249,13 @@ public class ImageService : IImageService } 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); } @@ -269,7 +290,7 @@ public class ImageService : IImageService } catch (Exception ex) { - _logger.LogError(ex, "Error downloading favicon.png for ${Domain}", domain); + _logger.LogError(ex, "Error downloading favicon.png for {Domain}", domain); throw; } } diff --git a/API/Services/ReadingListService.cs b/API/Services/ReadingListService.cs index 17a726767..24496e927 100644 --- a/API/Services/ReadingListService.cs +++ b/API/Services/ReadingListService.cs @@ -496,12 +496,13 @@ public class ReadingListService : IReadingListService } readingList.Items = items; + + if (!_unitOfWork.HasChanges()) continue; + await CalculateReadingListAgeRating(readingList); - if (_unitOfWork.HasChanges()) - { - await _unitOfWork.CommitAsync(); - } - await CalculateStartAndEndDates(await _unitOfWork.ReadingListRepository.GetReadingListByTitleAsync(arcPair.Item1, user.Id, ReadingListIncludes.Items | ReadingListIncludes.ItemChapter)); + await _unitOfWork.CommitAsync(); // TODO: See if we can avoid this extra commit by reworking bottom logic + await CalculateStartAndEndDates(await _unitOfWork.ReadingListRepository.GetReadingListByTitleAsync(arcPair.Item1, + user.Id, ReadingListIncludes.Items | ReadingListIncludes.ItemChapter)); await _unitOfWork.CommitAsync(); } } diff --git a/API/Services/Tasks/Scanner/Parser/Parser.cs b/API/Services/Tasks/Scanner/Parser/Parser.cs index 19bff0fe9..23ed4a7b6 100644 --- a/API/Services/Tasks/Scanner/Parser/Parser.cs +++ b/API/Services/Tasks/Scanner/Parser/Parser.cs @@ -321,13 +321,9 @@ public static class Parser new Regex( @"(?.*)( ?- ?)Ch\.\d+-?\d*", MatchOptions, RegexTimeout), - // [BAA]_Darker_than_Black_Omake-1.zip + // [BAA]_Darker_than_Black_Omake-1, Bleach 001-002, Kodoja #001 (March 2016) new Regex( - @"^(?!Vol)(?.*)(-)\d+-?\d*", // This catches a lot of stuff ^(?!Vol)(?.*)( |_)(\d+) - MatchOptions, RegexTimeout), - // Kodoja #001 (March 2016) - new Regex( - @"(?.*)(\s|_|-)#", + @"^(?!Vol)(?!Chapter)(?.+?)(-|_|\s|#)\d+(-\d+)?", 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) new Regex( diff --git a/API/Services/Tasks/Scanner/ProcessSeries.cs b/API/Services/Tasks/Scanner/ProcessSeries.cs index 711ab8a64..480fcc56c 100644 --- a/API/Services/Tasks/Scanner/ProcessSeries.cs +++ b/API/Services/Tasks/Scanner/ProcessSeries.cs @@ -346,6 +346,8 @@ public class ProcessSeries : IProcessSeries } + #region People + // Handle People foreach (var chapter in chapters) { @@ -490,6 +492,8 @@ public class ProcessSeries : IProcessSeries } }); + #endregion + } public void UpdateVolumes(Series series, IList parsedInfos, bool forceUpdate = false) diff --git a/API/Services/Tasks/StatsService.cs b/API/Services/Tasks/StatsService.cs index 9cbb9fa74..42b7ddfc2 100644 --- a/API/Services/Tasks/StatsService.cs +++ b/API/Services/Tasks/StatsService.cs @@ -34,7 +34,7 @@ public class StatsService : IStatsService private readonly IUnitOfWork _unitOfWork; private readonly DataContext _context; private readonly IStatisticService _statisticService; - private const string ApiUrl = "http://localhost:5003"; + private const string ApiUrl = "https://stats.kavitareader.com"; public StatsService(ILogger logger, IUnitOfWork unitOfWork, DataContext context, IStatisticService statisticService) { diff --git a/API/Services/TokenService.cs b/API/Services/TokenService.cs index 85808a2bb..0af710597 100644 --- a/API/Services/TokenService.cs +++ b/API/Services/TokenService.cs @@ -90,12 +90,12 @@ public class TokenService : ITokenService Token = await CreateToken(user), RefreshToken = await CreateRefreshToken(user) }; - } catch (SecurityTokenExpiredException) + } catch (SecurityTokenExpiredException ex) { // Handle expired token return null; } - catch (Exception) + catch (Exception ex) { // Handle other exceptions return null; diff --git a/API/config/appsettings.Development.json b/API/config/appsettings.Development.json index 03041b961..e8803c5b1 100644 --- a/API/config/appsettings.Development.json +++ b/API/config/appsettings.Development.json @@ -2,5 +2,5 @@ "TokenKey": "super secret unguessable key that is longer because we require it", "Port": 5000, "IpAddresses": "", - "BaseUrl": "/joe/" -} + "BaseUrl": "/" +} \ No newline at end of file diff --git a/UI/Web/src/app/admin/manage-alerts/manage-alerts.component.html b/UI/Web/src/app/admin/manage-alerts/manage-alerts.component.html index d317a7806..2f1848515 100644 --- a/UI/Web/src/app/admin/manage-alerts/manage-alerts.component.html +++ b/UI/Web/src/app/admin/manage-alerts/manage-alerts.component.html @@ -1,6 +1,16 @@

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.

- +
+
+
+ +
+ + +
+
+
+
@@ -20,20 +30,22 @@ - - - - - - - + + + + + + + + +
No issues
- {{item.extension}} - - {{item.filePath}} - - {{item.comment}} - - {{item.details}} -
No issues
+ {{item.extension}} + + {{item.filePath}} + + {{item.comment}} + + {{item.details}} +
\ No newline at end of file diff --git a/UI/Web/src/app/admin/manage-alerts/manage-alerts.component.ts b/UI/Web/src/app/admin/manage-alerts/manage-alerts.component.ts index 5e164348c..cee5c601b 100644 --- a/UI/Web/src/app/admin/manage-alerts/manage-alerts.component.ts +++ b/UI/Web/src/app/admin/manage-alerts/manage-alerts.component.ts @@ -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 { SortEvent, SortableHeader, compare } from 'src/app/_single-module/table/_directives/sortable-header.directive'; import { KavitaMediaError } from '../_models/media-error'; import { ServerService } from 'src/app/_services/server.service'; import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service'; +import { FormControl, FormGroup } from '@angular/forms'; @Component({ selector: 'app-manage-alerts', @@ -13,6 +14,7 @@ import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service }) export class ManageAlertsComponent implements OnInit { + @Output() alertCount = new EventEmitter(); @ViewChildren(SortableHeader) headers!: QueryList>; private readonly serverService = inject(ServerService); private readonly messageHub = inject(MessageHubService); @@ -25,6 +27,9 @@ export class ManageAlertsComponent implements OnInit { data: Array = []; isLoading = true; + formGroup = new FormGroup({ + filter: new FormControl('', []) + }); constructor() {} @@ -63,8 +68,7 @@ export class ManageAlertsComponent implements OnInit { this.serverService.getMediaErrors().subscribe(d => { this.data = d; this.isLoading = false; - console.log(this.data) - console.log(this.isLoading) + this.alertCount.emit(d.length); this.cdRef.detectChanges(); }); } @@ -72,4 +76,9 @@ export class ManageAlertsComponent implements OnInit { clear() { 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; + } } diff --git a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html index 0b4485cea..3523c055f 100644 --- a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html +++ b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html @@ -37,13 +37,13 @@ - + - Media Issues + Media Issues ({{alertCount}}) - + diff --git a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts index dbbd0a8b8..6762d7b37 100644 --- a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts +++ b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts @@ -18,6 +18,8 @@ export class ManageMediaSettingsComponent implements OnInit { serverSettings!: ServerSettings; settingsForm: FormGroup = new FormGroup({}); + alertCount: number = 0; + get EncodeFormats() { return EncodeFormats; } constructor(private settingsService: SettingsService, private toastr: ToastrService, private modalService: NgbModal, ) { } diff --git a/UI/Web/src/app/shared/_services/download.service.ts b/UI/Web/src/app/shared/_services/download.service.ts index d935b2228..a6439ba1b 100644 --- a/UI/Web/src/app/shared/_services/download.service.ts +++ b/UI/Web/src/app/shared/_services/download.service.ts @@ -55,6 +55,7 @@ export class DownloadService { private downloadsSource: BehaviorSubject = new BehaviorSubject([]); public activeDownloads$ = this.downloadsSource.asObservable(); + constructor(private httpClient: HttpClient, private confirmService: ConfirmService, @Inject(SAVER) private save: Saver, private accountService: AccountService) { } @@ -159,7 +160,7 @@ export class DownloadService { ).pipe( throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }), download((blob, filename) => { - this.save(blob, filename); + this.save(blob, decodeURIComponent(filename)); }), tap((d) => this.updateDownloadState(d, downloadType, subtitle)), finalize(() => this.finalizeDownloadState(downloadType, subtitle)) @@ -174,7 +175,7 @@ export class DownloadService { ).pipe( throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }), download((blob, filename) => { - this.save(blob, filename); + this.save(blob, decodeURIComponent(filename)); }), tap((d) => this.updateDownloadState(d, downloadType, subtitle)), finalize(() => this.finalizeDownloadState(downloadType, subtitle)) @@ -213,7 +214,7 @@ export class DownloadService { ).pipe( throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }), download((blob, filename) => { - this.save(blob, filename); + this.save(blob, decodeURIComponent(filename)); }), tap((d) => this.updateDownloadState(d, downloadType, subtitle)), finalize(() => this.finalizeDownloadState(downloadType, subtitle)) @@ -228,7 +229,7 @@ export class DownloadService { ).pipe( throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }), download((blob, filename) => { - this.save(blob, filename); + this.save(blob, decodeURIComponent(filename)); }), tap((d) => this.updateDownloadState(d, downloadType, subtitle)), finalize(() => this.finalizeDownloadState(downloadType, subtitle)) @@ -248,7 +249,7 @@ export class DownloadService { ).pipe( throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }), download((blob, filename) => { - this.save(blob, filename); + this.save(blob, decodeURIComponent(filename)); }), tap((d) => this.updateDownloadState(d, downloadType, subtitle)), finalize(() => this.finalizeDownloadState(downloadType, subtitle)) diff --git a/entrypoint.sh b/entrypoint.sh index f2cd02426..2f55d5e3a 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -27,6 +27,9 @@ if [ ! -f "/kavita/config/appsettings.json" ]; then fi fi +echo "App setting permissions" +echo ls -l "/kavita/config/appsettings.json" + chmod +x Kavita ./Kavita diff --git a/openapi.json b/openapi.json index 9e7058045..593debb5b 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,7 @@ "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.7.2.6" + "version": "0.7.2.7" }, "servers": [ {