mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Download Support (#298)
* Implemented the ability to download files (series, volume, chapter) * Added RBS checks to ensure user is either an admin or has download role * Added the ability to change a users feature RBS. Changed the Role seed to use reflection
This commit is contained in:
parent
4ae9f078b0
commit
16a77fa8d6
@ -38,6 +38,10 @@ namespace API.Tests.Comparers
|
||||
new[] {"Batman - Black white vol 1 #04.cbr", "Batman - Black white vol 1 #03.cbr", "Batman - Black white vol 1 #01.cbr", "Batman - Black white vol 1 #02.cbr"},
|
||||
new[] {"Batman - Black white vol 1 #01.cbr", "Batman - Black white vol 1 #02.cbr", "Batman - Black white vol 1 #03.cbr", "Batman - Black white vol 1 #04.cbr"}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"3and4.cbz", "The World God Only Knows - Oneshot.cbz", "5.cbz", "1and2.cbz"},
|
||||
new[] {"1and2.cbz", "3and4.cbz", "5.cbz", "The World God Only Knows - Oneshot.cbz"}
|
||||
)]
|
||||
public void TestNaturalSortComparer(string[] input, string[] expected)
|
||||
{
|
||||
Array.Sort(input, _nc);
|
||||
|
@ -16,11 +16,12 @@ namespace API.Tests.Services
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
private readonly ArchiveService _archiveService;
|
||||
private readonly ILogger<ArchiveService> _logger = Substitute.For<ILogger<ArchiveService>>();
|
||||
private readonly ILogger<DirectoryService> _directoryServiceLogger = Substitute.For<ILogger<DirectoryService>>();
|
||||
|
||||
public ArchiveServiceTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
_archiveService = new ArchiveService(_logger);
|
||||
_archiveService = new ArchiveService(_logger, new DirectoryService(_directoryServiceLogger));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -154,7 +155,7 @@ namespace API.Tests.Services
|
||||
[InlineData("sorting.zip", "sorting.expected.jpg")]
|
||||
public void GetCoverImage_Default_Test(string inputFile, string expectedOutputFile)
|
||||
{
|
||||
var archiveService = Substitute.For<ArchiveService>(_logger);
|
||||
var archiveService = Substitute.For<ArchiveService>(_logger, new DirectoryService(_directoryServiceLogger));
|
||||
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages");
|
||||
var expectedBytes = File.ReadAllBytes(Path.Join(testDirectory, expectedOutputFile));
|
||||
archiveService.Configure().CanOpen(Path.Join(testDirectory, inputFile)).Returns(ArchiveLibrary.Default);
|
||||
@ -174,7 +175,7 @@ namespace API.Tests.Services
|
||||
[InlineData("sorting.zip", "sorting.expected.jpg")]
|
||||
public void GetCoverImage_SharpCompress_Test(string inputFile, string expectedOutputFile)
|
||||
{
|
||||
var archiveService = Substitute.For<ArchiveService>(_logger);
|
||||
var archiveService = Substitute.For<ArchiveService>(_logger, new DirectoryService(_directoryServiceLogger));
|
||||
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages");
|
||||
var expectedBytes = File.ReadAllBytes(Path.Join(testDirectory, expectedOutputFile));
|
||||
|
||||
|
@ -4,5 +4,9 @@
|
||||
{
|
||||
public const string AdminRole = "Admin";
|
||||
public const string PlebRole = "Pleb";
|
||||
/// <summary>
|
||||
/// Used to give a user ability to download files from the server
|
||||
/// </summary>
|
||||
public const string DownloadRole = "Download";
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.DTOs;
|
||||
@ -150,5 +151,45 @@ namespace API.Controllers
|
||||
Preferences = _mapper.Map<UserPreferencesDto>(user.UserPreferences)
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("roles")]
|
||||
public ActionResult<IList<string>> GetRoles()
|
||||
{
|
||||
return typeof(PolicyConstants)
|
||||
.GetFields(BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(f => f.FieldType == typeof(string))
|
||||
.ToDictionary(f => f.Name,
|
||||
f => (string) f.GetValue(null)).Values.ToList();
|
||||
}
|
||||
|
||||
[HttpPost("update-rbs")]
|
||||
public async Task<ActionResult> UpdateRoles(UpdateRbsDto updateRbsDto)
|
||||
{
|
||||
var user = await _userManager.Users
|
||||
.Include(u => u.UserPreferences)
|
||||
//.Include(u => u.UserRoles)
|
||||
.SingleOrDefaultAsync(x => x.NormalizedUserName == updateRbsDto.Username.ToUpper());
|
||||
if (updateRbsDto.Roles.Contains(PolicyConstants.AdminRole) ||
|
||||
updateRbsDto.Roles.Contains(PolicyConstants.PlebRole))
|
||||
{
|
||||
return BadRequest("Invalid Roles");
|
||||
}
|
||||
|
||||
var existingRoles = (await _userManager.GetRolesAsync(user))
|
||||
.Where(s => s != PolicyConstants.AdminRole && s != PolicyConstants.PlebRole)
|
||||
.ToList();
|
||||
|
||||
// Find what needs to be added and what needs to be removed
|
||||
var rolesToRemove = existingRoles.Except(updateRbsDto.Roles);
|
||||
var result = await _userManager.AddToRolesAsync(user, updateRbsDto.Roles);
|
||||
|
||||
if (!result.Succeeded) return BadRequest("Something went wrong, unable to update user's roles");
|
||||
if ((await _userManager.RemoveFromRolesAsync(user, rolesToRemove)).Succeeded)
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
return BadRequest("Something went wrong, unable to update user's roles");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
96
API/Controllers/DownloadController.cs
Normal file
96
API/Controllers/DownloadController.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers
|
||||
{
|
||||
[Authorize(Policy = "RequireDownloadRole")]
|
||||
public class DownloadController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IArchiveService _archiveService;
|
||||
|
||||
public DownloadController(IUnitOfWork unitOfWork, IArchiveService archiveService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_archiveService = archiveService;
|
||||
}
|
||||
|
||||
[HttpGet("volume-size")]
|
||||
public async Task<ActionResult<long>> GetVolumeSize(int volumeId)
|
||||
{
|
||||
var files = await _unitOfWork.VolumeRepository.GetFilesForVolume(volumeId);
|
||||
return Ok(DirectoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
}
|
||||
|
||||
[HttpGet("chapter-size")]
|
||||
public async Task<ActionResult<long>> GetChapterSize(int chapterId)
|
||||
{
|
||||
var files = await _unitOfWork.VolumeRepository.GetFilesForChapter(chapterId);
|
||||
return Ok(DirectoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
}
|
||||
|
||||
[HttpGet("series-size")]
|
||||
public async Task<ActionResult<long>> GetSeriesSize(int seriesId)
|
||||
{
|
||||
var files = await _unitOfWork.SeriesRepository.GetFilesForSeries(seriesId);
|
||||
return Ok(DirectoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
}
|
||||
|
||||
[HttpGet("volume")]
|
||||
public async Task<ActionResult> DownloadVolume(int volumeId)
|
||||
{
|
||||
var files = await _unitOfWork.VolumeRepository.GetFilesForVolume(volumeId);
|
||||
try
|
||||
{
|
||||
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
|
||||
$"download_{User.GetUsername()}_v{volumeId}");
|
||||
return File(fileBytes, "application/zip", Path.GetFileName(zipPath));
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("chapter")]
|
||||
public async Task<ActionResult> DownloadChapter(int chapterId)
|
||||
{
|
||||
var files = await _unitOfWork.VolumeRepository.GetFilesForChapter(chapterId);
|
||||
try
|
||||
{
|
||||
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
|
||||
$"download_{User.GetUsername()}_c{chapterId}");
|
||||
return File(fileBytes, "application/zip", Path.GetFileName(zipPath));
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("series")]
|
||||
public async Task<ActionResult> DownloadSeries(int seriesId)
|
||||
{
|
||||
var files = await _unitOfWork.SeriesRepository.GetFilesForSeries(seriesId);
|
||||
try
|
||||
{
|
||||
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files.Select(c => c.FilePath),
|
||||
$"download_{User.GetUsername()}_s{seriesId}");
|
||||
return File(fileBytes, "application/zip", Path.GetFileName(zipPath));
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
||||
using API.Extensions;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@ -19,19 +20,19 @@ namespace API.Controllers
|
||||
private readonly IHostApplicationLifetime _applicationLifetime;
|
||||
private readonly ILogger<ServerController> _logger;
|
||||
private readonly IConfiguration _config;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IBackupService _backupService;
|
||||
private readonly IArchiveService _archiveService;
|
||||
|
||||
public ServerController(IHostApplicationLifetime applicationLifetime, ILogger<ServerController> logger, IConfiguration config,
|
||||
IDirectoryService directoryService, IBackupService backupService)
|
||||
IBackupService backupService, IArchiveService archiveService)
|
||||
{
|
||||
_applicationLifetime = applicationLifetime;
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_directoryService = directoryService;
|
||||
_backupService = backupService;
|
||||
_archiveService = archiveService;
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("restart")]
|
||||
public ActionResult RestartServer()
|
||||
{
|
||||
@ -45,33 +46,17 @@ namespace API.Controllers
|
||||
public async Task<ActionResult> GetLogs()
|
||||
{
|
||||
var files = _backupService.LogFiles(_config.GetMaxRollingFiles(), _config.GetLoggingFileName());
|
||||
|
||||
var tempDirectory = Path.Join(Directory.GetCurrentDirectory(), "temp");
|
||||
var dateString = DateTime.Now.ToShortDateString().Replace("/", "_");
|
||||
|
||||
var tempLocation = Path.Join(tempDirectory, "logs_" + dateString);
|
||||
DirectoryService.ExistOrCreate(tempLocation);
|
||||
if (!_directoryService.CopyFilesToDirectory(files, tempLocation))
|
||||
{
|
||||
return BadRequest("Unable to copy files to temp directory for log download.");
|
||||
}
|
||||
|
||||
var zipPath = Path.Join(tempDirectory, $"kavita_logs_{dateString}.zip");
|
||||
try
|
||||
{
|
||||
ZipFile.CreateFromDirectory(tempLocation, zipPath);
|
||||
var (fileBytes, zipPath) = await _archiveService.CreateZipForDownload(files, "logs");
|
||||
return File(fileBytes, "application/zip", Path.GetFileName(zipPath));
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an issue when archiving library backup");
|
||||
return BadRequest("There was an issue when archiving library backup");
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
var fileBytes = await _directoryService.ReadFileAsync(zipPath);
|
||||
|
||||
DirectoryService.ClearAndDeleteDirectory(tempLocation);
|
||||
(new FileInfo(zipPath)).Delete();
|
||||
|
||||
return File(fileBytes, "application/zip", Path.GetFileName(zipPath));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
public class SettingsController : BaseApiController
|
||||
{
|
||||
private readonly ILogger<SettingsController> _logger;
|
||||
@ -31,7 +31,7 @@ namespace API.Controllers
|
||||
_taskScheduler = taskScheduler;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<ActionResult<ServerSettingDto>> GetSettings()
|
||||
{
|
||||
@ -40,8 +40,7 @@ namespace API.Controllers
|
||||
settingsDto.LoggingLevel = Configuration.GetLogLevel(Program.GetAppSettingFilename());
|
||||
return Ok(settingsDto);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<ActionResult<ServerSettingDto>> UpdateSettings(ServerSettingDto updateSettingsDto)
|
||||
{
|
||||
@ -103,22 +102,19 @@ namespace API.Controllers
|
||||
_taskScheduler.ScheduleTasks();
|
||||
return Ok(updateSettingsDto);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
|
||||
[HttpGet("task-frequencies")]
|
||||
public ActionResult<IEnumerable<string>> GetTaskFrequencies()
|
||||
{
|
||||
return Ok(CronConverter.Options);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpGet("library-types")]
|
||||
public ActionResult<IEnumerable<string>> GetLibraryTypes()
|
||||
{
|
||||
return Ok(Enum.GetNames(typeof(LibraryType)));
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpGet("log-levels")]
|
||||
public ActionResult<IEnumerable<string>> GetLogLevels()
|
||||
{
|
||||
|
10
API/DTOs/UpdateRBSDto.cs
Normal file
10
API/DTOs/UpdateRBSDto.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class UpdateRbsDto
|
||||
{
|
||||
public string Username { get; init; }
|
||||
public IList<string> Roles { get; init; }
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.Entities;
|
||||
@ -15,11 +16,13 @@ namespace API.Data
|
||||
{
|
||||
public static async Task SeedRoles(RoleManager<AppRole> roleManager)
|
||||
{
|
||||
var roles = new List<AppRole>
|
||||
{
|
||||
new() {Name = PolicyConstants.AdminRole},
|
||||
new() {Name = PolicyConstants.PlebRole}
|
||||
};
|
||||
var roles = typeof(PolicyConstants)
|
||||
.GetFields(BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(f => f.FieldType == typeof(string))
|
||||
.ToDictionary(f => f.Name,
|
||||
f => (string) f.GetValue(null)).Values
|
||||
.Select(policyName => new AppRole() {Name = policyName})
|
||||
.ToList();
|
||||
|
||||
foreach (var role in roles)
|
||||
{
|
||||
|
@ -411,5 +411,16 @@ namespace API.Data
|
||||
|
||||
return await PagedList<SeriesDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
||||
}
|
||||
|
||||
public async Task<IList<MangaFile>> GetFilesForSeries(int seriesId)
|
||||
{
|
||||
return await _context.Volume
|
||||
.Where(v => v.SeriesId == seriesId)
|
||||
.Include(v => v.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.SelectMany(v => v.Chapters.SelectMany(c => c.Files))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
@ -65,6 +65,8 @@ namespace API.Data
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public async Task<ChapterDto> GetChapterDtoAsync(int chapterId)
|
||||
{
|
||||
@ -84,5 +86,15 @@ namespace API.Data
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<MangaFile>> GetFilesForVolume(int volumeId)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => volumeId == c.VolumeId)
|
||||
.Include(c => c.Files)
|
||||
.SelectMany(c => c.Files)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
@ -39,6 +39,7 @@ namespace API.Extensions
|
||||
services.AddAuthorization(opt =>
|
||||
{
|
||||
opt.AddPolicy("RequireAdminRole", policy => policy.RequireRole(PolicyConstants.AdminRole));
|
||||
opt.AddPolicy("RequireDownloadRole", policy => policy.RequireRole(PolicyConstants.DownloadRole, PolicyConstants.AdminRole));
|
||||
});
|
||||
|
||||
return services;
|
||||
|
@ -61,5 +61,6 @@ namespace API.Interfaces
|
||||
Task<PagedList<SeriesDto>> GetRecentlyAdded(int libraryId, int userId, UserParams userParams);
|
||||
Task<SeriesMetadataDto> GetSeriesMetadata(int seriesId);
|
||||
Task<PagedList<SeriesDto>> GetSeriesDtoForCollectionAsync(int collectionId, int userId, UserParams userParams);
|
||||
Task<IList<MangaFile>> GetFilesForSeries(int seriesId);
|
||||
}
|
||||
}
|
@ -13,5 +13,6 @@ namespace API.Interfaces
|
||||
Task<IList<MangaFile>> GetFilesForChapter(int chapterId);
|
||||
Task<IList<Chapter>> GetChaptersAsync(int volumeId);
|
||||
Task<byte[]> GetChapterCoverImageAsync(int chapterId);
|
||||
Task<IList<MangaFile>> GetFilesForVolume(int volumeId);
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
using System.IO.Compression;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
using API.Archive;
|
||||
using API.Entities;
|
||||
|
||||
namespace API.Interfaces.Services
|
||||
{
|
||||
@ -12,5 +16,6 @@ namespace API.Interfaces.Services
|
||||
string GetSummaryInfo(string archivePath);
|
||||
ArchiveLibrary CanOpen(string archivePath);
|
||||
bool ArchiveNeedsFlattening(ZipArchive archive);
|
||||
Task<Tuple<byte[], string>> CreateZipForDownload(IEnumerable<string> files, string tempFolder);
|
||||
}
|
||||
}
|
@ -4,12 +4,14 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
using API.Archive;
|
||||
using API.Comparators;
|
||||
using API.Extensions;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services.Tasks;
|
||||
using Kavita.Common;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IO;
|
||||
using SharpCompress.Archives;
|
||||
@ -25,13 +27,15 @@ namespace API.Services
|
||||
public class ArchiveService : IArchiveService
|
||||
{
|
||||
private readonly ILogger<ArchiveService> _logger;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private const int ThumbnailWidth = 320; // 153w x 230h
|
||||
private static readonly RecyclableMemoryStreamManager StreamManager = new();
|
||||
private readonly NaturalSortComparer _comparer;
|
||||
|
||||
public ArchiveService(ILogger<ArchiveService> logger)
|
||||
public ArchiveService(ILogger<ArchiveService> logger, IDirectoryService directoryService)
|
||||
{
|
||||
_logger = logger;
|
||||
_directoryService = directoryService;
|
||||
_comparer = new NaturalSortComparer();
|
||||
}
|
||||
|
||||
@ -216,7 +220,39 @@ namespace API.Services
|
||||
!Path.HasExtension(archive.Entries.ElementAt(0).FullName) ||
|
||||
archive.Entries.Any(e => e.FullName.Contains(Path.AltDirectorySeparatorChar) && !Parser.Parser.HasBlacklistedFolderInPath(e.FullName));
|
||||
}
|
||||
|
||||
|
||||
public async Task<Tuple<byte[], string>> CreateZipForDownload(IEnumerable<string> files, string tempFolder)
|
||||
{
|
||||
var tempDirectory = Path.Join(Directory.GetCurrentDirectory(), "temp");
|
||||
var dateString = DateTime.Now.ToShortDateString().Replace("/", "_");
|
||||
|
||||
var tempLocation = Path.Join(tempDirectory, $"{tempFolder}_{dateString}");
|
||||
DirectoryService.ExistOrCreate(tempLocation);
|
||||
if (!_directoryService.CopyFilesToDirectory(files, tempLocation))
|
||||
{
|
||||
throw new KavitaException("Unable to copy files to temp directory archive download.");
|
||||
}
|
||||
|
||||
var zipPath = Path.Join(tempDirectory, $"kavita_{tempFolder}_{dateString}.zip");
|
||||
try
|
||||
{
|
||||
ZipFile.CreateFromDirectory(tempLocation, zipPath);
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an issue creating temp archive");
|
||||
throw new KavitaException("There was an issue creating temp archive");
|
||||
}
|
||||
|
||||
|
||||
var fileBytes = await _directoryService.ReadFileAsync(zipPath);
|
||||
|
||||
DirectoryService.ClearAndDeleteDirectory(tempLocation);
|
||||
(new FileInfo(zipPath)).Delete();
|
||||
|
||||
return Tuple.Create(fileBytes, zipPath);
|
||||
}
|
||||
|
||||
private byte[] CreateThumbnail(string entryName, Stream stream, string formatExtension = ".jpg")
|
||||
{
|
||||
if (!formatExtension.StartsWith("."))
|
||||
|
@ -102,6 +102,16 @@ namespace API.Services
|
||||
return !Directory.Exists(path) ? Array.Empty<string>() : Directory.GetFiles(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the total number of bytes for a given set of full file paths
|
||||
/// </summary>
|
||||
/// <param name="paths"></param>
|
||||
/// <returns>Total bytes</returns>
|
||||
public static long GetTotalSize(IEnumerable<string> paths)
|
||||
{
|
||||
return paths.Sum(path => new FileInfo(path).Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the path exists and is a directory. If path does not exist, this will create it. Returns false in all fail cases.
|
||||
/// </summary>
|
||||
|
Loading…
x
Reference in New Issue
Block a user