Bugfix/release cleanup (#512)

* Lots of cleanup on the warnings in the solution. Deprecated IsLastWriteLessThan and made a new method HasFileBeenModifiedSince.

* Added some tests for the new extension method.

* Changed filter import to use correct import

* Scan Series now uses Refresh Metadata for Series, rather than library one.

* Fixed an issue where cover generation wasn't properly taking forced update into consideration. Removed a case of cover generation for no reason.

* Fixed series downloads not triggering backend call
This commit is contained in:
Joseph Milazzo 2021-08-21 10:03:47 -07:00 committed by GitHub
parent 2a3a08de74
commit 0d2d73e8ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 116 additions and 131 deletions

View File

@ -1,21 +1,33 @@
namespace API.Tests.Extensions
using System;
using System.Globalization;
using System.IO;
using API.Extensions;
using Xunit;
namespace API.Tests.Extensions
{
public class FileInfoExtensionsTests
{
// [Fact]
// public void DoesLastWriteMatchTest()
// {
// var fi = Substitute.For<FileInfo>();
// fi.LastWriteTime = DateTime.Now;
//
// var deltaTime = DateTime.Today.Subtract(TimeSpan.FromDays(1));
// Assert.False(fi.DoesLastWriteMatch(deltaTime));
// }
//
// [Fact]
// public void IsLastWriteLessThanTest()
// {
//
// }
private static readonly string TestDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Extensions/Test Data/");
[Fact]
public void HasFileBeenModifiedSince_ShouldBeFalse()
{
var filepath = Path.Join(TestDirectory, "not modified.txt");
var date = new FileInfo(filepath).LastWriteTime;
Assert.False(new FileInfo(filepath).HasFileBeenModifiedSince(date));
File.ReadAllText(filepath);
Assert.False(new FileInfo(filepath).HasFileBeenModifiedSince(date));
}
[Fact]
public void HasFileBeenModifiedSince_ShouldBeTrue()
{
var filepath = Path.Join(TestDirectory, "modified on run.txt");
var date = new FileInfo(filepath).LastWriteTime;
Assert.False(new FileInfo(filepath).HasFileBeenModifiedSince(date));
File.AppendAllLines(filepath, new[] { DateTime.Now.ToString(CultureInfo.InvariantCulture) });
Assert.True(new FileInfo(filepath).HasFileBeenModifiedSince(date));
}
}
}
}

View File

@ -0,0 +1,2 @@
This file should be modified by the unit test08/20/2021 10:26:03
08/20/2021 10:26:29

View File

@ -0,0 +1 @@
Hello, this file should not be modified

View File

@ -64,6 +64,7 @@ namespace API.Tests.Parser
[InlineData("Sword Art Online Vol 10 - Alicization Running [Yen Press] [LuCaZ] {r2}.epub", "10")]
[InlineData("Noblesse - Episode 406 (52 Pages).7z", "0")]
[InlineData("X-Men v1 #201 (September 2007).cbz", "1")]
[InlineData("Hentai Ouji to Warawanai Neko. - Vol. 06 Ch. 034.5", "6")]
public void ParseVolumeTest(string filename, string expected)
{
Assert.Equal(expected, API.Parser.Parser.ParseVolume(filename));
@ -154,6 +155,7 @@ namespace API.Tests.Parser
[InlineData("Please Go Home, Akutsu-San! - Chapter 038.5 - Volume Announcement.cbz", "Please Go Home, Akutsu-San!")]
[InlineData("Killing Bites - Vol 11 Chapter 050 Save Me, Nunupi!.cbz", "Killing Bites")]
[InlineData("Mad Chimera World - Volume 005 - Chapter 026.cbz", "Mad Chimera World")]
[InlineData("Hentai Ouji to Warawanai Neko. - Vol. 06 Ch. 034.5", "Hentai Ouji to Warawanai Neko.")]
public void ParseSeriesTest(string filename, string expected)
{
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
@ -222,6 +224,7 @@ namespace API.Tests.Parser
[InlineData("Boku No Kokoro No Yabai Yatsu - Chapter 054 I Prayed At The Shrine (V0).cbz", "54")]
[InlineData("Ijousha No Ai - Vol.01 Chapter 029 8 Years Ago", "29")]
[InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 09.cbz", "9")]
[InlineData("Hentai Ouji to Warawanai Neko. - Vol. 06 Ch. 034.5", "34.5")]
public void ParseChaptersTest(string filename, string expected)
{
Assert.Equal(expected, API.Parser.Parser.ParseChapter(filename));

View File

@ -33,7 +33,6 @@ namespace API.Tests.Services
private readonly IBookService _bookService = Substitute.For<IBookService>();
private readonly IImageService _imageService = Substitute.For<IImageService>();
private readonly ILogger<MetadataService> _metadataLogger = Substitute.For<ILogger<MetadataService>>();
private readonly IDirectoryService _directoryService = Substitute.For<IDirectoryService>();
private readonly ICacheService _cacheService = Substitute.For<ICacheService>();
private readonly DbConnection _connection;

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs;

View File

@ -70,7 +70,7 @@ namespace API.Controllers
{
return await GetFirstFileDownload(files);
}
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
var (fileBytes, _) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
$"download_{User.GetUsername()}_v{volumeId}");
return File(fileBytes, DefaultContentType, $"{series.Name} - Volume {volume.Number}.zip");
}
@ -116,7 +116,7 @@ namespace API.Controllers
{
return await GetFirstFileDownload(files);
}
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
var (fileBytes, _) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
$"download_{User.GetUsername()}_c{chapterId}");
return File(fileBytes, DefaultContentType, $"{series.Name} - Chapter {chapter.Number}.zip");
}
@ -137,7 +137,7 @@ namespace API.Controllers
{
return await GetFirstFileDownload(files);
}
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
var (fileBytes, _) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
$"download_{User.GetUsername()}_s{seriesId}");
return File(fileBytes, DefaultContentType, $"{series.Name}.zip");
}
@ -194,11 +194,11 @@ namespace API.Controllers
var files = _directoryService.GetFilesWithExtension(chapterExtractPath, Parser.Parser.ImageFileExtensions);
// Filter out images that aren't in bookmarks
Array.Sort(files, _numericComparer);
totalFilePaths.AddRange(files.Where((t, i) => chapterPages.Contains(i)));
totalFilePaths.AddRange(files.Where((_, i) => chapterPages.Contains(i)));
}
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(totalFilePaths,
var (fileBytes, _) = await _archiveService.CreateZipForDownload(totalFilePaths,
tempFolder);
DirectoryService.ClearAndDeleteDirectory(fullExtractPath);
return File(fileBytes, DefaultContentType, $"{series.Name} - Bookmarks.zip");

View File

@ -368,7 +368,7 @@ namespace API.Controllers
/// <summary>
/// Removes all bookmarks for all chapters linked to a Series
/// </summary>
/// <param name="seriesId"></param>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("remove-bookmarks")]
public async Task<ActionResult> RemoveBookmarks(RemoveBookmarkForSeriesDto dto)

View File

@ -12,7 +12,6 @@ using API.Interfaces;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Controllers

View File

@ -5,7 +5,6 @@ using System.Threading.Tasks;
using API.DTOs.Stats;
using API.DTOs.Update;
using API.Extensions;
using API.Interfaces;
using API.Interfaces.Services;
using API.Services.Tasks;
using Kavita.Common;
@ -26,11 +25,10 @@ namespace API.Controllers
private readonly IBackupService _backupService;
private readonly IArchiveService _archiveService;
private readonly ICacheService _cacheService;
private readonly ITaskScheduler _taskScheduler;
private readonly IVersionUpdaterService _versionUpdaterService;
public ServerController(IHostApplicationLifetime applicationLifetime, ILogger<ServerController> logger, IConfiguration config,
IBackupService backupService, IArchiveService archiveService, ICacheService cacheService, ITaskScheduler taskScheduler,
IBackupService backupService, IArchiveService archiveService, ICacheService cacheService,
IVersionUpdaterService versionUpdaterService)
{
_applicationLifetime = applicationLifetime;
@ -39,7 +37,6 @@ namespace API.Controllers
_backupService = backupService;
_archiveService = archiveService;
_cacheService = cacheService;
_taskScheduler = taskScheduler;
_versionUpdaterService = versionUpdaterService;
}

View File

@ -1,6 +1,4 @@
using System.Collections.Generic;
namespace API.DTOs
namespace API.DTOs
{
public class SeriesByIdsDto
{

View File

@ -1,6 +1,5 @@
using API.Entities;
using API.Interfaces.Repositories;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
namespace API.Data

View File

@ -60,11 +60,11 @@ namespace API.Data
}
await context.SaveChangesAsync();
// Port and LoggingLevel are managed in appSettings.json. Update the DB values to match
context.ServerSetting.FirstOrDefault(s => s.Key == ServerSettingKey.Port).Value =
context.ServerSetting.First(s => s.Key == ServerSettingKey.Port).Value =
Configuration.Port + string.Empty;
context.ServerSetting.FirstOrDefault(s => s.Key == ServerSettingKey.LoggingLevel).Value =
context.ServerSetting.First(s => s.Key == ServerSettingKey.LoggingLevel).Value =
Configuration.LogLevel + string.Empty;
await context.SaveChangesAsync();
@ -74,11 +74,11 @@ namespace API.Data
public static async Task SeedSeriesMetadata(DataContext context)
{
await context.Database.EnsureCreatedAsync();
context.Database.EnsureCreated();
var series = await context.Series
.Include(s => s.Metadata).ToListAsync();
foreach (var s in series)
{
s.Metadata ??= new SeriesMetadata();

View File

@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Comparators;
using API.DTOs;
using API.DTOs.Filtering;
using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Helpers;
using API.Interfaces;

View File

@ -1,9 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs;
using API.DTOs.Reader;
using API.Entities;
using API.Interfaces;
using AutoMapper;

View File

@ -2,7 +2,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using API.Entities.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace API.Entities
{

View File

@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using API.Entities.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace API.Entities

View File

@ -1,5 +1,4 @@
using System;
using System.IO;
using System.IO;
using System.Linq;
using API.Comparators;

View File

@ -5,14 +5,15 @@ namespace API.Extensions
{
public static class FileInfoExtensions
{
public static bool DoesLastWriteMatch(this FileInfo fileInfo, DateTime comparison)
/// <summary>
/// Checks if the last write time of the file is after the passed date
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="comparison"></param>
/// <returns></returns>
public static bool HasFileBeenModifiedSince(this FileInfo fileInfo, DateTime comparison)
{
return comparison.Equals(fileInfo.LastWriteTime);
}
public static bool IsLastWriteLessThan(this FileInfo fileInfo, DateTime comparison)
{
return fileInfo.LastWriteTime < comparison;
return fileInfo?.LastWriteTime > comparison;
}
}
}

View File

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using API.DTOs.Update;
using API.Services.Tasks;
namespace API.Interfaces.Services
{

View File

@ -30,6 +30,7 @@ namespace API.Services
private readonly IDirectoryService _directoryService;
private static readonly RecyclableMemoryStreamManager StreamManager = new();
private readonly NaturalSortComparer _comparer;
private const string ComicInfoFilename = "comicinfo";
public ArchiveService(ILogger<ArchiveService> logger, IDirectoryService directoryService)
{
@ -297,7 +298,7 @@ namespace API.Services
foreach (var entry in entries)
{
var filename = Path.GetFileNameWithoutExtension(entry.Key).ToLower();
if (filename.EndsWith("comicinfo")
if (filename.EndsWith(ComicInfoFilename)
&& !filename.StartsWith(Parser.Parser.MacOsMetadataFileStartsWith)
&& !Parser.Parser.HasBlacklistedFolderInPath(entry.Key)
&& Parser.Parser.IsXml(entry.Key))
@ -334,7 +335,7 @@ namespace API.Services
_logger.LogDebug("Using default compression handling");
using var archive = ZipFile.OpenRead(archivePath);
var entry = archive.Entries.SingleOrDefault(x => !Parser.Parser.HasBlacklistedFolderInPath(x.FullName)
&& Path.GetFileNameWithoutExtension(x.Name).ToLower() == "comicinfo"
&& Path.GetFileNameWithoutExtension(x.Name)?.ToLower() == ComicInfoFilename
&& !Path.GetFileNameWithoutExtension(x.Name).StartsWith(Parser.Parser.MacOsMetadataFileStartsWith)
&& Parser.Parser.IsXml(x.FullName));
if (entry != null)

View File

@ -9,7 +9,6 @@ using API.Entities.Enums;
using API.Extensions;
using API.Interfaces;
using API.Interfaces.Services;
using Kavita.Common;
using Microsoft.Extensions.Logging;
namespace API.Services

View File

@ -11,7 +11,9 @@ namespace API.Services.Clients
{
private readonly HttpClient _client;
private readonly ILogger<StatsApiClient> _logger;
#pragma warning disable S1075
private const string ApiUrl = "http://stats.kavitareader.com";
#pragma warning restore S1075
public StatsApiClient(HttpClient client, ILogger<StatsApiClient> logger)
{

View File

@ -320,7 +320,7 @@ namespace API.Services
var fileCount = 0;
// Determine whether to parallelize file processing on each folder based on processor count.
var procCount = Environment.ProcessorCount;
//var procCount = Environment.ProcessorCount;
// Data structure to hold names of subfolders to be examined for files.
var dirs = new Stack<string>();

View File

@ -67,7 +67,10 @@ namespace API.Services
public void UpdateMetadata(Chapter chapter, bool forceUpdate)
{
var firstFile = chapter.Files.OrderBy(x => x.Chapter).FirstOrDefault();
if (!chapter.CoverImageLocked && ShouldFindCoverImage(chapter.CoverImage, forceUpdate) && firstFile != null && !new FileInfo(firstFile.FilePath).IsLastWriteLessThan(firstFile.LastModified))
if (!chapter.CoverImageLocked
&& ShouldFindCoverImage(chapter.CoverImage, forceUpdate)
&& firstFile != null
&& (forceUpdate || new FileInfo(firstFile.FilePath).HasFileBeenModifiedSince(firstFile.LastModified)))
{
chapter.Files ??= new List<MangaFile>();
chapter.CoverImage = GetCoverImage(firstFile);
@ -88,19 +91,7 @@ namespace API.Services
if (firstChapter == null) return;
// Skip calculating Cover Image (I/O) if the chapter already has it set
if (!firstChapter.CoverImageLocked && ShouldFindCoverImage(firstChapter.CoverImage, forceUpdate))
{
// NOTE: Why do I do this? By the time this method gets executed, the chapter has already been calculated for
// Plus how can we have a volume without at least 1 chapter?
var firstFile = firstChapter.Files.OrderBy(x => x.Chapter).FirstOrDefault();
if (firstFile != null && !new FileInfo(firstFile.FilePath).IsLastWriteLessThan(firstFile.LastModified))
{
firstChapter.CoverImage = GetCoverImage(firstFile);
}
}
volume.CoverImage = firstChapter.CoverImage;
}
/// <summary>

View File

@ -119,7 +119,6 @@ namespace API.Services
BackgroundJob.Enqueue(() => _cleanupService.Cleanup());
}
public void CleanupChapters(int[] chapterIds)
{
BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds));

View File

@ -74,7 +74,7 @@ namespace API.Services.Tasks
totalFiles, parsedSeries.Keys.Count, sw.ElapsedMilliseconds + scanElapsedTime, series.Name);
CleanupDbEntities();
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadata(libraryId, forceUpdate));
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadataForSeries(libraryId, seriesId));
BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds));
}
else
@ -132,11 +132,14 @@ namespace API.Services.Tasks
{
ScanLibrary(lib.Id, false);
}
}
/// <summary>
/// Scans a library for file changes. If force update passed, all entities will be rechecked for new cover images and comicInfo.xml changes.
/// Scans a library for file changes.
/// Will kick off a scheduled background task to refresh metadata,
/// ie) all entities will be rechecked for new cover images and comicInfo.xml changes
/// </summary>
/// <param name="libraryId"></param>
/// <param name="forceUpdate"></param>

View File

@ -42,7 +42,7 @@ namespace API.Services.Tasks
{
public override HttpMessageHandler CreateMessageHandler() {
return new HttpClientHandler {
ServerCertificateCustomValidationCallback = (a, b, c, d) => true
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
}
}
@ -87,7 +87,7 @@ namespace API.Services.Tasks
return updates.Select(CreateDto);
}
private UpdateNotificationDto? CreateDto(GithubReleaseMetadata update)
private UpdateNotificationDto CreateDto(GithubReleaseMetadata update)
{
if (update == null || string.IsNullOrEmpty(update.Tag_Name)) return null;
var version = update.Tag_Name.Replace("v", string.Empty);

View File

@ -49,7 +49,7 @@ namespace API
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Kavita API", Version = "v1" });
var filePath = Path.Combine(System.AppContext.BaseDirectory, "API.xml");
var filePath = Path.Combine(AppContext.BaseDirectory, "API.xml");
c.IncludeXmlComments(filePath);
});
services.AddResponseCompression(options =>

View File

@ -8,7 +8,7 @@ namespace Kavita.Common
{
public static class Configuration
{
private static string AppSettingsFilename = GetAppSettingFilename();
private static readonly string AppSettingsFilename = GetAppSettingFilename();
public static string Branch
{
get => GetBranch(GetAppSettingFilename());
@ -53,6 +53,7 @@ namespace Kavita.Common
var json = File.ReadAllText(filePath);
var jsonObj = JsonSerializer.Deserialize<dynamic>(json);
const string key = "TokenKey";
if (jsonObj == null) return string.Empty;
if (jsonObj.TryGetProperty(key, out JsonElement tokenElement))
{
@ -69,7 +70,7 @@ namespace Kavita.Common
return string.Empty;
}
private static bool SetJwtToken(string filePath, string token)
private static void SetJwtToken(string filePath, string token)
{
try
{
@ -77,53 +78,37 @@ namespace Kavita.Common
var json = File.ReadAllText(filePath)
.Replace("\"TokenKey\": \"" + currentToken, "\"TokenKey\": \"" + token);
File.WriteAllText(filePath, json);
return true;
}
catch (Exception)
{
return false;
/* Swallow exception */
}
}
public static bool CheckIfJwtTokenSet()
{
//string filePath
try
{
return GetJwtToken(GetAppSettingFilename()) != "super secret unguessable key";
}
catch (Exception ex)
{
Console.WriteLine("Error writing app settings: " + ex.Message);
}
try
{
return GetJwtToken(GetAppSettingFilename()) != "super secret unguessable key";
}
catch (Exception ex)
{
Console.WriteLine("Error writing app settings: " + ex.Message);
}
return false;
return false;
}
public static bool UpdateJwtToken(string token)
{
try
{
var filePath = GetAppSettingFilename();
var json = File.ReadAllText(filePath).Replace("super secret unguessable key", token);
File.WriteAllText(GetAppSettingFilename(), json);
return true;
}
catch (Exception)
{
return false;
}
}
#endregion
#region Port
public static bool SetPort(string filePath, int port)
private static void SetPort(string filePath, int port)
{
if (new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker)
{
return true;
return;
}
try
@ -131,15 +116,14 @@ namespace Kavita.Common
var currentPort = GetPort(filePath);
var json = File.ReadAllText(filePath).Replace("\"Port\": " + currentPort, "\"Port\": " + port);
File.WriteAllText(filePath, json);
return true;
}
catch (Exception)
{
return false;
/* Swallow Exception */
}
}
public static int GetPort(string filePath)
private static int GetPort(string filePath)
{
const int defaultPort = 5000;
if (new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker)
@ -152,6 +136,7 @@ namespace Kavita.Common
var json = File.ReadAllText(filePath);
var jsonObj = JsonSerializer.Deserialize<dynamic>(json);
const string key = "Port";
if (jsonObj == null) return defaultPort;
if (jsonObj.TryGetProperty(key, out JsonElement tokenElement))
{
@ -170,7 +155,7 @@ namespace Kavita.Common
#region LogLevel
public static bool SetLogLevel(string filePath, string logLevel)
private static void SetLogLevel(string filePath, string logLevel)
{
try
{
@ -178,20 +163,21 @@ namespace Kavita.Common
var json = File.ReadAllText(filePath)
.Replace($"\"Default\": \"{currentLevel}\"", $"\"Default\": \"{logLevel}\"");
File.WriteAllText(filePath, json);
return true;
}
catch (Exception)
{
return false;
/* Swallow Exception */
}
}
public static string GetLogLevel(string filePath)
private static string GetLogLevel(string filePath)
{
try
{
var json = File.ReadAllText(filePath);
var jsonObj = JsonSerializer.Deserialize<dynamic>(json);
if (jsonObj == null) return string.Empty;
if (jsonObj.TryGetProperty("Logging", out JsonElement tokenElement))
{
foreach (var property in tokenElement.EnumerateObject())
@ -217,7 +203,7 @@ namespace Kavita.Common
#endregion
public static string GetBranch(string filePath)
private static string GetBranch(string filePath)
{
const string defaultBranch = "main";
@ -226,6 +212,7 @@ namespace Kavita.Common
var json = File.ReadAllText(filePath);
var jsonObj = JsonSerializer.Deserialize<dynamic>(json);
const string key = "Branch";
if (jsonObj == null) return string.Empty;
if (jsonObj.TryGetProperty(key, out JsonElement tokenElement))
{
@ -240,7 +227,7 @@ namespace Kavita.Common
return defaultBranch;
}
public static bool SetBranch(string filePath, string updatedBranch)
private static void SetBranch(string filePath, string updatedBranch)
{
try
{
@ -248,11 +235,10 @@ namespace Kavita.Common
var json = File.ReadAllText(filePath)
.Replace("\"Branch\": " + currentBranch, "\"Branch\": " + updatedBranch);
File.WriteAllText(filePath, json);
return true;
}
catch (Exception)
{
return false;
/* Swallow Exception */
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Runtime.Serialization;
namespace Kavita.Common
{
@ -9,13 +10,16 @@ namespace Kavita.Common
public class KavitaException : Exception
{
public KavitaException()
{
}
{ }
public KavitaException(string message) : base(message)
{
{ }
}
public KavitaException(string message, Exception inner)
: base(message, inner) { }
protected KavitaException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}

View File

@ -7,7 +7,7 @@ import { MessageHubService } from './_services/message-hub.service';
import { NavService } from './_services/nav.service';
import { PresenceHubService } from './_services/presence-hub.service';
import { StatsService } from './_services/stats.service';
import 'rxjs/add/operator/filter';
import { filter } from 'rxjs/operators';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
@Component({
@ -23,7 +23,7 @@ export class AppComponent implements OnInit {
// Close any open modals when a route change occurs
router.events
.filter(event => event instanceof NavigationStart)
.pipe(filter(event => event instanceof NavigationStart))
.subscribe((event) => {
if (this.ngbModal.hasOpenModals()) {
this.ngbModal.dismissAll();

View File

@ -461,7 +461,7 @@ export class SeriesDetailComponent implements OnInit {
}),
finalize(() => {
this.downloadInProgress = false;
}));
})).subscribe(() => {/* No Operation */});;
});
}
}