Downloading Enhancements (#2599)

This commit is contained in:
Joe Milazzo 2024-01-11 14:08:57 -06:00 committed by GitHub
parent e6f6090fcf
commit 70cb687ef6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 139 additions and 45 deletions

View file

@ -118,7 +118,7 @@ public class DownloadController : BaseApiController
return await _accountService.HasDownloadPermission(user);
}
private ActionResult GetFirstFileDownload(IEnumerable<MangaFile> files)
private PhysicalFileResult GetFirstFileDownload(IEnumerable<MangaFile> files)
{
var (zipFile, contentType, fileDownloadName) = _downloadService.GetFirstFileDownload(files);
return PhysicalFile(zipFile, contentType, Uri.EscapeDataString(fileDownloadName), true);
@ -150,31 +150,40 @@ public class DownloadController : BaseApiController
private async Task<ActionResult> DownloadFiles(ICollection<MangaFile> files, string tempFolder, string downloadName)
{
var username = User.GetUsername();
var filename = Path.GetFileNameWithoutExtension(downloadName);
try
{
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
MessageFactory.DownloadProgressEvent(User.GetUsername(),
Path.GetFileNameWithoutExtension(downloadName), 0F, "started"));
MessageFactory.DownloadProgressEvent(username,
filename, $"Downloading {filename}", 0F, "started"));
if (files.Count == 1)
{
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
MessageFactory.DownloadProgressEvent(User.GetUsername(),
Path.GetFileNameWithoutExtension(downloadName), 1F, "ended"));
MessageFactory.DownloadProgressEvent(username,
filename, $"Downloading {filename}",1F, "ended"));
return GetFirstFileDownload(files);
}
var filePath = _archiveService.CreateZipForDownload(files.Select(c => c.FilePath), tempFolder);
var filePath = _archiveService.CreateZipFromFoldersForDownload(files.Select(c => c.FilePath).ToList(), tempFolder, ProgressCallback);
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
MessageFactory.DownloadProgressEvent(User.GetUsername(),
Path.GetFileNameWithoutExtension(downloadName), 1F, "ended"));
MessageFactory.DownloadProgressEvent(username,
filename, "Download Complete", 1F, "ended"));
return PhysicalFile(filePath, DefaultContentType, Uri.EscapeDataString(downloadName), true);
async Task ProgressCallback(Tuple<string, float> progressInfo)
{
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
MessageFactory.DownloadProgressEvent(username, filename, $"Extracting {Path.GetFileNameWithoutExtension(progressInfo.Item1)}",
Math.Clamp(progressInfo.Item2, 0F, 1F)));
}
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an exception when trying to download files");
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
MessageFactory.DownloadProgressEvent(User.GetUsername(),
Path.GetFileNameWithoutExtension(downloadName), 1F, "ended"));
filename, "Download Complete", 1F, "ended"));
throw;
}
}
@ -216,12 +225,12 @@ public class DownloadController : BaseApiController
var filename = $"{series!.Name} - Bookmarks.zip";
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
MessageFactory.DownloadProgressEvent(username, Path.GetFileNameWithoutExtension(filename), 0F));
MessageFactory.DownloadProgressEvent(username, Path.GetFileNameWithoutExtension(filename), $"Downloading {filename}",0F));
var seriesIds = string.Join("_", downloadBookmarkDto.Bookmarks.Select(b => b.SeriesId).Distinct());
var filePath = _archiveService.CreateZipForDownload(files,
$"download_{userId}_{seriesIds}_bookmarks");
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
MessageFactory.DownloadProgressEvent(username, Path.GetFileNameWithoutExtension(filename), 1F));
MessageFactory.DownloadProgressEvent(username, Path.GetFileNameWithoutExtension(filename), $"Downloading {filename}", 1F));
return PhysicalFile(filePath, DefaultContentType, System.Web.HttpUtility.UrlEncode(filename), true);

View file

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Xml.Serialization;
using API.Archive;
@ -30,13 +31,21 @@ public interface IArchiveService
ArchiveLibrary CanOpen(string archivePath);
bool ArchiveNeedsFlattening(ZipArchive archive);
/// <summary>
/// Creates a zip file form the listed files and outputs to the temp folder.
/// Creates a zip file form the listed files and outputs to the temp folder. This will combine into one zip of multiple zips.
/// </summary>
/// <param name="files">List of files to be zipped up. Should be full file paths.</param>
/// <param name="tempFolder">Temp folder name to use for preparing the files. Will be created and deleted</param>
/// <returns>Path to the temp zip</returns>
/// <exception cref="KavitaException"></exception>
string CreateZipForDownload(IEnumerable<string> files, string tempFolder);
/// <summary>
/// Creates a zip file form the listed files and outputs to the temp folder. This will extract each archive and combine them into one zip.
/// </summary>
/// <param name="files">List of files to be zipped up. Should be full file paths.</param>
/// <param name="tempFolder">Temp folder name to use for preparing the files. Will be created and deleted</param>
/// <returns>Path to the temp zip</returns>
/// <exception cref="KavitaException"></exception>
string CreateZipFromFoldersForDownload(IList<string> files, string tempFolder, Func<Tuple<string, float>, Task> progressCallback);
}
/// <summary>
@ -322,6 +331,54 @@ public class ArchiveService : IArchiveService
return zipPath;
}
public string CreateZipFromFoldersForDownload(IList<string> files, string tempFolder, Func<Tuple<string, float>, Task> progressCallback)
{
var dateString = DateTime.UtcNow.ToShortDateString().Replace("/", "_");
var potentialExistingFile = _directoryService.FileSystem.FileInfo.New(Path.Join(_directoryService.TempDirectory, $"kavita_{tempFolder}_{dateString}.zip"));
if (potentialExistingFile.Exists)
{
// A previous download exists, just return it immediately
return potentialExistingFile.FullName;
}
// Extract all the files to a temp directory and create zip on that
var tempLocation = Path.Join(_directoryService.TempDirectory, $"{tempFolder}_{dateString}");
var totalFiles = files.Count + 1;
var count = 1f;
try
{
_directoryService.ExistOrCreate(tempLocation);
foreach (var path in files)
{
var tempPath = Path.Join(tempLocation, _directoryService.FileSystem.Path.GetFileNameWithoutExtension(_directoryService.FileSystem.FileInfo.New(path).Name));
_directoryService.ExistOrCreate(tempPath);
progressCallback(Tuple.Create(_directoryService.FileSystem.FileInfo.New(path).Name, (1.0f * totalFiles) / count));
ExtractArchive(path, tempPath);
count++;
}
}
catch
{
throw new KavitaException("bad-copy-files-for-download");
}
var zipPath = Path.Join(_directoryService.TempDirectory, $"kavita_{tempFolder}_{dateString}.zip");
try
{
ZipFile.CreateFromDirectory(tempLocation, zipPath);
// Remove the folder as we have the zip
_directoryService.ClearAndDeleteDirectory(tempLocation);
}
catch (AggregateException ex)
{
_logger.LogError(ex, "There was an issue creating temp archive");
throw new KavitaException("generic-create-temp-archive");
}
return zipPath;
}
/// <summary>
/// Test if the archive path exists and an archive
@ -477,7 +534,7 @@ public class ArchiveService : IArchiveService
{
if (!IsValidArchive(archivePath)) return;
if (Directory.Exists(extractPath)) return;
if (_directoryService.FileSystem.Directory.Exists(extractPath)) return;
if (!_directoryService.FileSystem.File.Exists(archivePath))
{

View file

@ -376,13 +376,13 @@ public static class MessageFactory
};
}
public static SignalRMessage DownloadProgressEvent(string username, string downloadName, float progress, string eventType = "updated")
public static SignalRMessage DownloadProgressEvent(string username, string downloadName, string subtitle, float progress, string eventType = "updated")
{
return new SignalRMessage()
{
Name = DownloadProgress,
Title = $"Downloading {downloadName}",
SubTitle = $"Preparing {username.SentenceCase()} the download of {downloadName}",
Title = $"Preparing {username.SentenceCase()} the download of {downloadName}",
SubTitle = subtitle,
EventType = eventType,
Progress = ProgressType.Determinate,
Body = new