mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Hotfix 0.5.2.5 - PDF Rendering Regression on Pi 32-bit (#1212)
* PDF Rendering on Pi (64bit) & Backup Fix (#1204) * Updated dependencies. SharpCompress has been updated to v2.1.0 which should fix pdf rendering on pi/arm64 devices. * Removed some dependencies not needed and updated the Backup code to account for themes and ensure everything gets copied every time. * Bump versions by dotnet-bump-version. * Hotfix Prep (#1211) * Patched cover image change that somehow got missed * Fixed a bug where clicking bottom action bar buttons on book reader wouldn't work correctly (would close drawer when trying to open) * Bump versions by dotnet-bump-version. * Fixed some missing merge stuff
This commit is contained in:
parent
29c4cc7915
commit
fbf8b6c1dd
@ -7,10 +7,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.2" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.3" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||||
<PackageReference Include="NSubstitute" Version="4.3.0" />
|
<PackageReference Include="NSubstitute" Version="4.3.0" />
|
||||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="16.1.15" />
|
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="16.1.25" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.IO.Abstractions.TestingHelpers;
|
using System.IO.Abstractions.TestingHelpers;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
@ -36,6 +37,9 @@ public class BackupServiceTests
|
|||||||
private const string CoverImageDirectory = "C:/kavita/config/covers/";
|
private const string CoverImageDirectory = "C:/kavita/config/covers/";
|
||||||
private const string BackupDirectory = "C:/kavita/config/backups/";
|
private const string BackupDirectory = "C:/kavita/config/backups/";
|
||||||
private const string LogDirectory = "C:/kavita/config/logs/";
|
private const string LogDirectory = "C:/kavita/config/logs/";
|
||||||
|
private const string ConfigDirectory = "C:/kavita/config/";
|
||||||
|
private const string BookmarkDirectory = "C:/kavita/config/bookmarks";
|
||||||
|
private const string ThemesDirectory = "C:/kavita/config/theme";
|
||||||
|
|
||||||
public BackupServiceTests()
|
public BackupServiceTests()
|
||||||
{
|
{
|
||||||
@ -110,6 +114,8 @@ public class BackupServiceTests
|
|||||||
fileSystem.AddDirectory(CoverImageDirectory);
|
fileSystem.AddDirectory(CoverImageDirectory);
|
||||||
fileSystem.AddDirectory(BackupDirectory);
|
fileSystem.AddDirectory(BackupDirectory);
|
||||||
fileSystem.AddDirectory(LogDirectory);
|
fileSystem.AddDirectory(LogDirectory);
|
||||||
|
fileSystem.AddDirectory(ThemesDirectory);
|
||||||
|
fileSystem.AddDirectory(BookmarkDirectory);
|
||||||
fileSystem.AddDirectory("C:/data/");
|
fileSystem.AddDirectory("C:/data/");
|
||||||
|
|
||||||
return fileSystem;
|
return fileSystem;
|
||||||
@ -121,6 +127,7 @@ public class BackupServiceTests
|
|||||||
|
|
||||||
#region GetLogFiles
|
#region GetLogFiles
|
||||||
|
|
||||||
|
[Fact]
|
||||||
public void GetLogFiles_ExpectAllFiles_NoRollingFiles()
|
public void GetLogFiles_ExpectAllFiles_NoRollingFiles()
|
||||||
{
|
{
|
||||||
var filesystem = CreateFileSystem();
|
var filesystem = CreateFileSystem();
|
||||||
@ -128,16 +135,82 @@ public class BackupServiceTests
|
|||||||
filesystem.AddFile($"{LogDirectory}kavita1.log", new MockFileData(""));
|
filesystem.AddFile($"{LogDirectory}kavita1.log", new MockFileData(""));
|
||||||
|
|
||||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||||
// You can't mock _config extensions because they are static
|
var inMemorySettings = new Dictionary<string, string> {
|
||||||
_config.GetMaxRollingFiles().Returns(1);
|
{"Logging:File:Path", "config/logs/kavita.log"},
|
||||||
_config.GetLoggingFileName().Returns(ds.FileSystem.Path.Join(LogDirectory, "kavita.log"));
|
{"Logging:File:MaxRollingFiles", "0"},
|
||||||
|
};
|
||||||
|
IConfiguration configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(inMemorySettings)
|
||||||
|
.Build();
|
||||||
|
|
||||||
var backupService = new BackupService(_logger, _unitOfWork, ds, _config, _messageHub);
|
var backupService = new BackupService(_logger, _unitOfWork, ds, configuration, _messageHub);
|
||||||
|
|
||||||
Assert.Single(backupService.GetLogFiles(1, LogDirectory));
|
var backupLogFiles = backupService.GetLogFiles(0, LogDirectory).ToList();
|
||||||
|
Assert.Single(backupLogFiles);
|
||||||
|
Assert.Equal(API.Parser.Parser.NormalizePath($"{LogDirectory}kavita.log"), API.Parser.Parser.NormalizePath(backupLogFiles.First()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetLogFiles_ExpectAllFiles_WithRollingFiles()
|
||||||
|
{
|
||||||
|
var filesystem = CreateFileSystem();
|
||||||
|
filesystem.AddFile($"{LogDirectory}kavita.log", new MockFileData(""));
|
||||||
|
filesystem.AddFile($"{LogDirectory}kavita1.log", new MockFileData(""));
|
||||||
|
|
||||||
|
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||||
|
var inMemorySettings = new Dictionary<string, string> {
|
||||||
|
{"Logging:File:Path", "config/logs/kavita.log"},
|
||||||
|
{"Logging:File:MaxRollingFiles", "1"},
|
||||||
|
};
|
||||||
|
IConfiguration configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(inMemorySettings)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var backupService = new BackupService(_logger, _unitOfWork, ds, configuration, _messageHub);
|
||||||
|
|
||||||
|
var backupLogFiles = backupService.GetLogFiles(1, LogDirectory).Select(API.Parser.Parser.NormalizePath).ToList();
|
||||||
|
Assert.NotEmpty(backupLogFiles.Where(file => file.Equals(API.Parser.Parser.NormalizePath($"{LogDirectory}kavita.log")) || file.Equals(API.Parser.Parser.NormalizePath($"{LogDirectory}kavita1.log"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region BackupFiles
|
||||||
|
|
||||||
|
// I don't think I can unit test this due to ZipFile.Create
|
||||||
|
// [Fact]
|
||||||
|
// public async Task BackupDatabase_ExpectAllFiles()
|
||||||
|
// {
|
||||||
|
// var filesystem = CreateFileSystem();
|
||||||
|
// filesystem.AddFile($"{LogDirectory}kavita.log", new MockFileData(""));
|
||||||
|
// filesystem.AddFile($"{ConfigDirectory}kavita.db", new MockFileData(""));
|
||||||
|
// filesystem.AddFile($"{CoverImageDirectory}1.png", new MockFileData(""));
|
||||||
|
// filesystem.AddFile($"{BookmarkDirectory}1.png", new MockFileData(""));
|
||||||
|
// filesystem.AddFile($"{ConfigDirectory}appsettings.json", new MockFileData(""));
|
||||||
|
// filesystem.AddFile($"{ThemesDirectory}joe.css", new MockFileData(""));
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||||
|
// var inMemorySettings = new Dictionary<string, string> {
|
||||||
|
// {"Logging:File:Path", $"{LogDirectory}kavita.log"},
|
||||||
|
// {"Logging:File:MaxRollingFiles", "0"},
|
||||||
|
// };
|
||||||
|
// IConfiguration configuration = new ConfigurationBuilder()
|
||||||
|
// .AddInMemoryCollection(inMemorySettings)
|
||||||
|
// .Build();
|
||||||
|
//
|
||||||
|
// var backupService = new BackupService(_logger, _unitOfWork, ds, configuration, _messageHub);
|
||||||
|
//
|
||||||
|
// await backupService.BackupDatabase();
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// var files = ds.GetFiles(BackupDirectory).ToList();
|
||||||
|
// Assert.NotEmpty(files);
|
||||||
|
// var zipFile = files.FirstOrDefault();
|
||||||
|
// Assert.NotNull(zipFile);
|
||||||
|
// using var zipArchive = ZipFile.OpenRead(zipFile);
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -508,6 +508,21 @@ namespace API.Tests.Services
|
|||||||
Assert.Equal(2, ds.GetFiles("/manga/output/").Count());
|
Assert.Equal(2, ds.GetFiles("/manga/output/").Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CopyFilesToDirectory_ShouldMoveAllFilesAndNotFailOnNonExistentFiles()
|
||||||
|
{
|
||||||
|
const string testDirectory = "/manga/";
|
||||||
|
var fileSystem = new MockFileSystem();
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
fileSystem.AddFile($"{testDirectory}file_{i}.zip", new MockFileData(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||||
|
ds.CopyFilesToDirectory(new []{$"{testDirectory}file_{0}.zip", $"{testDirectory}file_{200}.zip", $"{testDirectory}file_{1}.zip"}, "/manga/output/");
|
||||||
|
Assert.Equal(2, ds.GetFiles("/manga/output/").Count());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CopyFilesToDirectory_ShouldMoveAllFiles_InclFilesInNestedFolders()
|
public void CopyFilesToDirectory_ShouldMoveAllFiles_InclFilesInNestedFolders()
|
||||||
{
|
{
|
||||||
|
@ -48,32 +48,30 @@
|
|||||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.42" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.42" />
|
||||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||||
<PackageReference Include="MediatR" Version="10.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.3" />
|
||||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.3" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.3" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.2" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.2" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.2" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.0" />
|
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.0" />
|
||||||
<PackageReference Include="NetVips" Version="2.1.0" />
|
<PackageReference Include="NetVips" Version="2.1.0" />
|
||||||
<PackageReference Include="NetVips.Native" Version="8.12.2" />
|
<PackageReference Include="NetVips.Native" Version="8.12.2" />
|
||||||
<PackageReference Include="NReco.Logging.File" Version="1.1.4" />
|
<PackageReference Include="NReco.Logging.File" Version="1.1.4" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
<PackageReference Include="SharpCompress" Version="0.31.0" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.0.0" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.0" />
|
||||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.36.0.43782">
|
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.37.0.45539">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.0" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.16.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.17.0" />
|
||||||
<PackageReference Include="System.IO.Abstractions" Version="16.1.15" />
|
<PackageReference Include="System.IO.Abstractions" Version="16.1.25" />
|
||||||
<PackageReference Include="VersOne.Epub" Version="3.0.3.1" />
|
<PackageReference Include="VersOne.Epub" Version="3.0.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -86,6 +86,7 @@ namespace API.Controllers
|
|||||||
existingPreferences.BookReaderFontSize = preferencesDto.BookReaderFontSize;
|
existingPreferences.BookReaderFontSize = preferencesDto.BookReaderFontSize;
|
||||||
existingPreferences.BookReaderTapToPaginate = preferencesDto.BookReaderTapToPaginate;
|
existingPreferences.BookReaderTapToPaginate = preferencesDto.BookReaderTapToPaginate;
|
||||||
existingPreferences.BookReaderReadingDirection = preferencesDto.BookReaderReadingDirection;
|
existingPreferences.BookReaderReadingDirection = preferencesDto.BookReaderReadingDirection;
|
||||||
|
preferencesDto.Theme ??= await _unitOfWork.SiteThemeRepository.GetDefaultTheme();
|
||||||
existingPreferences.Theme = await _unitOfWork.SiteThemeRepository.GetThemeById(preferencesDto.Theme.Id);
|
existingPreferences.Theme = await _unitOfWork.SiteThemeRepository.GetThemeById(preferencesDto.Theme.Id);
|
||||||
|
|
||||||
// TODO: Remove this code - this overrides layout mode to be single until the mode is released
|
// TODO: Remove this code - this overrides layout mode to be single until the mode is released
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using MediatR;
|
|
||||||
|
|
||||||
namespace API.DTOs;
|
|
||||||
|
|
||||||
public class UpdateUserRole : IRequest<bool>
|
|
||||||
{
|
|
||||||
public string Username { get; init; }
|
|
||||||
public IList<string> Roles { get; init; }
|
|
||||||
}
|
|
@ -373,6 +373,11 @@ namespace API.Services
|
|||||||
{
|
{
|
||||||
currentFile = file;
|
currentFile = file;
|
||||||
|
|
||||||
|
if (!FileSystem.File.Exists(file))
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to copy {File} to {DirectoryPath} as it doesn't exist", file, directoryPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
var fileInfo = FileSystem.FileInfo.FromFileName(file);
|
var fileInfo = FileSystem.FileInfo.FromFileName(file);
|
||||||
var targetFile = FileSystem.FileInfo.FromFileName(RenameFileForCopy(file, directoryPath, prepend));
|
var targetFile = FileSystem.FileInfo.FromFileName(RenameFileForCopy(file, directoryPath, prepend));
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ public class BackupService : IBackupService
|
|||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
private readonly ILogger<BackupService> _logger;
|
private readonly ILogger<BackupService> _logger;
|
||||||
private readonly IDirectoryService _directoryService;
|
private readonly IDirectoryService _directoryService;
|
||||||
|
private readonly IConfiguration _config;
|
||||||
private readonly IEventHub _eventHub;
|
private readonly IEventHub _eventHub;
|
||||||
|
|
||||||
private readonly IList<string> _backupFiles;
|
private readonly IList<string> _backupFiles;
|
||||||
@ -41,11 +42,12 @@ public class BackupService : IBackupService
|
|||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_directoryService = directoryService;
|
_directoryService = directoryService;
|
||||||
|
_config = config;
|
||||||
_eventHub = eventHub;
|
_eventHub = eventHub;
|
||||||
|
|
||||||
var maxRollingFiles = config.GetMaxRollingFiles();
|
// var maxRollingFiles = config.GetMaxRollingFiles();
|
||||||
var loggingSection = config.GetLoggingFileName();
|
// var loggingSection = config.GetLoggingFileName();
|
||||||
var files = GetLogFiles(maxRollingFiles, loggingSection);
|
// var files = GetLogFiles(maxRollingFiles, loggingSection);
|
||||||
|
|
||||||
|
|
||||||
_backupFiles = new List<string>()
|
_backupFiles = new List<string>()
|
||||||
@ -58,12 +60,10 @@ public class BackupService : IBackupService
|
|||||||
"kavita.db-wal" // This wont always be there
|
"kavita.db-wal" // This wont always be there
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var file in files.Select(f => (_directoryService.FileSystem.FileInfo.FromFileName(f)).Name).ToList())
|
// foreach (var file in files.Select(f => (_directoryService.FileSystem.FileInfo.FromFileName(f)).Name))
|
||||||
{
|
// {
|
||||||
_backupFiles.Add(file);
|
// _backupFiles.Add(file);
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> GetLogFiles(int maxRollingFiles, string logFileName)
|
public IEnumerable<string> GetLogFiles(int maxRollingFiles, string logFileName)
|
||||||
@ -74,7 +74,7 @@ public class BackupService : IBackupService
|
|||||||
var files = maxRollingFiles > 0
|
var files = maxRollingFiles > 0
|
||||||
? _directoryService.GetFiles(_directoryService.LogDirectory,
|
? _directoryService.GetFiles(_directoryService.LogDirectory,
|
||||||
$@"{_directoryService.FileSystem.Path.GetFileNameWithoutExtension(fi.Name)}{multipleFileRegex}\.log")
|
$@"{_directoryService.FileSystem.Path.GetFileNameWithoutExtension(fi.Name)}{multipleFileRegex}\.log")
|
||||||
: new[] {"kavita.log"};
|
: new[] {_directoryService.FileSystem.Path.Join(_directoryService.LogDirectory, "kavita.log")};
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +97,7 @@ public class BackupService : IBackupService
|
|||||||
}
|
}
|
||||||
|
|
||||||
await SendProgress(0F, "Started backup");
|
await SendProgress(0F, "Started backup");
|
||||||
|
await SendProgress(0.1F, "Copying core files");
|
||||||
|
|
||||||
var dateString = $"{DateTime.Now.ToShortDateString()}_{DateTime.Now.ToLongTimeString()}".Replace("/", "_").Replace(":", "_");
|
var dateString = $"{DateTime.Now.ToShortDateString()}_{DateTime.Now.ToLongTimeString()}".Replace("/", "_").Replace(":", "_");
|
||||||
var zipPath = _directoryService.FileSystem.Path.Join(backupDirectory, $"kavita_backup_{dateString}.zip");
|
var zipPath = _directoryService.FileSystem.Path.Join(backupDirectory, $"kavita_backup_{dateString}.zip");
|
||||||
@ -116,15 +117,19 @@ public class BackupService : IBackupService
|
|||||||
_directoryService.CopyFilesToDirectory(
|
_directoryService.CopyFilesToDirectory(
|
||||||
_backupFiles.Select(file => _directoryService.FileSystem.Path.Join(_directoryService.ConfigDirectory, file)).ToList(), tempDirectory);
|
_backupFiles.Select(file => _directoryService.FileSystem.Path.Join(_directoryService.ConfigDirectory, file)).ToList(), tempDirectory);
|
||||||
|
|
||||||
await SendProgress(0.25F, "Copying core files");
|
CopyLogsToBackupDirectory(tempDirectory);
|
||||||
|
|
||||||
|
await SendProgress(0.25F, "Copying cover images");
|
||||||
|
|
||||||
await CopyCoverImagesToBackupDirectory(tempDirectory);
|
await CopyCoverImagesToBackupDirectory(tempDirectory);
|
||||||
|
|
||||||
await SendProgress(0.5F, "Copying cover images");
|
await SendProgress(0.5F, "Copying bookmarks");
|
||||||
|
|
||||||
await CopyBookmarksToBackupDirectory(tempDirectory);
|
await CopyBookmarksToBackupDirectory(tempDirectory);
|
||||||
|
|
||||||
await SendProgress(0.75F, "Copying bookmarks");
|
await SendProgress(0.75F, "Copying themes");
|
||||||
|
|
||||||
|
CopyThemesToBackupDirectory(tempDirectory);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -140,6 +145,14 @@ public class BackupService : IBackupService
|
|||||||
await SendProgress(1F, "Completed backup");
|
await SendProgress(1F, "Completed backup");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CopyLogsToBackupDirectory(string tempDirectory)
|
||||||
|
{
|
||||||
|
var maxRollingFiles = _config.GetMaxRollingFiles();
|
||||||
|
var loggingSection = _config.GetLoggingFileName();
|
||||||
|
var files = GetLogFiles(maxRollingFiles, loggingSection);
|
||||||
|
_directoryService.CopyFilesToDirectory(files, _directoryService.FileSystem.Path.Join(tempDirectory, "logs"));
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CopyCoverImagesToBackupDirectory(string tempDirectory)
|
private async Task CopyCoverImagesToBackupDirectory(string tempDirectory)
|
||||||
{
|
{
|
||||||
var outputTempDir = Path.Join(tempDirectory, "covers");
|
var outputTempDir = Path.Join(tempDirectory, "covers");
|
||||||
@ -193,6 +206,26 @@ public class BackupService : IBackupService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CopyThemesToBackupDirectory(string tempDirectory)
|
||||||
|
{
|
||||||
|
var outputTempDir = Path.Join(tempDirectory, "themes");
|
||||||
|
_directoryService.ExistOrCreate(outputTempDir);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_directoryService.CopyDirectoryToDirectory(_directoryService.SiteThemeDirectory, outputTempDir);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
// Swallow exception.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_directoryService.GetFiles(outputTempDir, searchOption: SearchOption.AllDirectories).Any())
|
||||||
|
{
|
||||||
|
_directoryService.ClearAndDeleteDirectory(outputTempDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SendProgress(float progress, string subtitle)
|
private async Task SendProgress(float progress, string subtitle)
|
||||||
{
|
{
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||||
|
@ -19,7 +19,6 @@ using Hangfire;
|
|||||||
using Hangfire.MemoryStorage;
|
using Hangfire.MemoryStorage;
|
||||||
using Kavita.Common;
|
using Kavita.Common;
|
||||||
using Kavita.Common.EnvironmentInfo;
|
using Kavita.Common.EnvironmentInfo;
|
||||||
using MediatR;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@ -132,8 +131,6 @@ namespace API
|
|||||||
// Add IHostedService for startup tasks
|
// Add IHostedService for startup tasks
|
||||||
// Any services that should be bootstrapped go here
|
// Any services that should be bootstrapped go here
|
||||||
services.AddHostedService<StartupTasksHostedService>();
|
services.AddHostedService<StartupTasksHostedService>();
|
||||||
|
|
||||||
services.AddMediatR(typeof(Startup));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<Company>kavitareader.com</Company>
|
<Company>kavitareader.com</Company>
|
||||||
<Product>Kavita</Product>
|
<Product>Kavita</Product>
|
||||||
<AssemblyVersion>0.5.2.3</AssemblyVersion>
|
<AssemblyVersion>0.5.2.5</AssemblyVersion>
|
||||||
<NeutralLanguage>en</NeutralLanguage>
|
<NeutralLanguage>en</NeutralLanguage>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<PackageReference Include="Flurl.Http" Version="3.2.2" />
|
<PackageReference Include="Flurl.Http" Version="3.2.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.36.0.43782">
|
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.37.0.45539">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@ -116,7 +116,7 @@
|
|||||||
<div class="left {{clickOverlayClass('left')}} no-observe" (click)="prevPage()" *ngIf="clickToPaginate" tabindex="-1"></div>
|
<div class="left {{clickOverlayClass('left')}} no-observe" (click)="prevPage()" *ngIf="clickToPaginate" tabindex="-1"></div>
|
||||||
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe" (click)="nextPage()" *ngIf="clickToPaginate" tabindex="-1"></div>
|
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe" (click)="nextPage()" *ngIf="clickToPaginate" tabindex="-1"></div>
|
||||||
|
|
||||||
<div *ngIf="page !== undefined && scrollbarNeeded">
|
<div *ngIf="page !== undefined && scrollbarNeeded" (click)="$event.stopPropagation();">
|
||||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -130,7 +130,7 @@
|
|||||||
<span class="d-none d-sm-block"> {{readingDirection === ReadingDirection.LeftToRight ? 'Previous' : 'Next'}}</span>
|
<span class="d-none d-sm-block"> {{readingDirection === ReadingDirection.LeftToRight ? 'Previous' : 'Next'}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="!this.adhocPageHistory.isEmpty()" class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="goBack()" title="Go Back"><i class="fa fa-reply" aria-hidden="true"></i><span class="d-none d-sm-block"> Go Back</span></button>
|
<button *ngIf="!this.adhocPageHistory.isEmpty()" class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="goBack()" title="Go Back"><i class="fa fa-reply" aria-hidden="true"></i><span class="d-none d-sm-block"> Go Back</span></button>
|
||||||
<button class="btn btn-secondary col-2 col-xs-1" (click)="toggleDrawer()"><i class="fa fa-bars" aria-hidden="true"></i><span class="d-none d-sm-block">Settings</span></button>
|
<button class="btn btn-secondary col-2 col-xs-1" (click)="toggleDrawer()"><i class="fa fa-bars" aria-hidden="true"></i><span class="d-none d-sm-block">Settings {{drawerOpen}}</span></button>
|
||||||
<div class="book-title col-2 d-none d-sm-block">
|
<div class="book-title col-2 d-none d-sm-block">
|
||||||
<ng-container *ngIf="isLoading; else showTitle">
|
<ng-container *ngIf="isLoading; else showTitle">
|
||||||
<div class="spinner-border spinner-border-sm text-primary" style="border-radius: 50%;" role="status">
|
<div class="spinner-border spinner-border-sm text-primary" style="border-radius: 50%;" role="status">
|
||||||
@ -138,7 +138,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #showTitle>
|
<ng-template #showTitle>
|
||||||
{{bookTitle}}
|
<span class="book-title-text">{{bookTitle}}</span>
|
||||||
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" aria-label="Incognito mode is on. Toggle to turn off.">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">Incognito Mode</span>)</span>
|
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" aria-label="Incognito mode is on. Toggle to turn off.">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">Incognito Mode</span>)</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, Inject, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
||||||
import { NgxFileDropEntry, FileSystemFileEntry } from 'ngx-file-drop';
|
import { NgxFileDropEntry, FileSystemFileEntry } from 'ngx-file-drop';
|
||||||
import { fromEvent, Subject } from 'rxjs';
|
import { fromEvent, Subject } from 'rxjs';
|
||||||
@ -7,6 +7,7 @@ import { ToastrService } from 'ngx-toastr';
|
|||||||
import { ImageService } from 'src/app/_services/image.service';
|
import { ImageService } from 'src/app/_services/image.service';
|
||||||
import { KEY_CODES } from 'src/app/shared/_services/utility.service';
|
import { KEY_CODES } from 'src/app/shared/_services/utility.service';
|
||||||
import { UploadService } from 'src/app/_services/upload.service';
|
import { UploadService } from 'src/app/_services/upload.service';
|
||||||
|
import { DOCUMENT } from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-cover-image-chooser',
|
selector: 'app-cover-image-chooser',
|
||||||
@ -42,7 +43,8 @@ export class CoverImageChooserComponent implements OnInit, OnDestroy {
|
|||||||
mode: 'file' | 'url' | 'all' = 'all';
|
mode: 'file' | 'url' | 'all' = 'all';
|
||||||
private readonly onDestroy = new Subject<void>();
|
private readonly onDestroy = new Subject<void>();
|
||||||
|
|
||||||
constructor(public imageService: ImageService, private fb: FormBuilder, private toastr: ToastrService, private uploadService: UploadService) { }
|
constructor(public imageService: ImageService, private fb: FormBuilder, private toastr: ToastrService, private uploadService: UploadService,
|
||||||
|
@Inject(DOCUMENT) private document: Document) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.form = this.fb.group({
|
this.form = this.fb.group({
|
||||||
@ -84,7 +86,7 @@ export class CoverImageChooserComponent implements OnInit, OnDestroy {
|
|||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.crossOrigin = 'Anonymous';
|
img.crossOrigin = 'Anonymous';
|
||||||
img.src = this.imageService.getCoverUploadImage(filename);
|
img.src = this.imageService.getCoverUploadImage(filename);
|
||||||
img.onload = (e) => this.handleUrlImageAdd(e);
|
img.onload = (e) => this.handleUrlImageAdd(img);
|
||||||
img.onerror = (e) => {
|
img.onerror = (e) => {
|
||||||
this.toastr.error('The image could not be fetched due to server refusing request. Please download and upload from file instead.');
|
this.toastr.error('The image could not be fetched due to server refusing request. Please download and upload from file instead.');
|
||||||
this.form.get('coverImageUrl')?.setValue('');
|
this.form.get('coverImageUrl')?.setValue('');
|
||||||
@ -97,6 +99,8 @@ export class CoverImageChooserComponent implements OnInit, OnDestroy {
|
|||||||
changeMode(mode: 'url') {
|
changeMode(mode: 'url') {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.setupEnterHandler();
|
this.setupEnterHandler();
|
||||||
|
|
||||||
|
setTimeout(() => (this.document.querySelector('#load-image') as HTMLInputElement)?.focus(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dropped(files: NgxFileDropEntry[]) {
|
public dropped(files: NgxFileDropEntry[]) {
|
||||||
@ -125,10 +129,8 @@ export class CoverImageChooserComponent implements OnInit, OnDestroy {
|
|||||||
this.selectedBase64Url.emit(e.target.result);
|
this.selectedBase64Url.emit(e.target.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUrlImageAdd(e: any) {
|
handleUrlImageAdd(img: HTMLImageElement) {
|
||||||
if (e.path === null || e.path.length === 0) return;
|
const url = this.getBase64Image(img);
|
||||||
|
|
||||||
const url = this.getBase64Image(e.path[0]);
|
|
||||||
this.imageUrls.push(url);
|
this.imageUrls.push(url);
|
||||||
this.imageUrlsChange.emit(this.imageUrls);
|
this.imageUrlsChange.emit(this.imageUrls);
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
|
||||||
import { ServerService } from '../_services/server.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
@ -11,23 +9,8 @@ import { ServerService } from '../_services/server.service';
|
|||||||
})
|
})
|
||||||
export class DashboardComponent implements OnInit {
|
export class DashboardComponent implements OnInit {
|
||||||
|
|
||||||
tabs: Array<{title: string, fragment: string}> = [
|
|
||||||
{title: 'Libraries', fragment: ''},
|
|
||||||
{title: 'Lists', fragment: 'lists'},
|
|
||||||
{title: 'Collections', fragment: 'collections'},
|
|
||||||
];
|
|
||||||
active = this.tabs[0];
|
|
||||||
|
|
||||||
constructor(public route: ActivatedRoute, private serverService: ServerService,
|
constructor(public route: ActivatedRoute, private titleService: Title) {
|
||||||
private toastr: ToastrService, private titleService: Title) {
|
|
||||||
this.route.fragment.subscribe(frag => {
|
|
||||||
const tab = this.tabs.filter(item => item.fragment === frag);
|
|
||||||
if (tab.length > 0) {
|
|
||||||
this.active = tab[0];
|
|
||||||
} else {
|
|
||||||
this.active = this.tabs[0]; // Default to first tab
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.titleService.setTitle('Kavita - Dashboard');
|
this.titleService.setTitle('Kavita - Dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user