mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-05-19 05:55:47 -04:00
#1823 - Address full disk better. Fail clearly and early when possible, fail safely when space cannot be queried, and do not take down the UI or spam the user when the disk is full.
This commit is contained in:
@@ -18,20 +18,17 @@ partial class MainVM
|
||||
public async Task BackupAllBooks()
|
||||
{
|
||||
var books = await Task.Run(DbContexts.GetUnliberated_Flat_NoTracking);
|
||||
BackupAllBooks(books);
|
||||
await BackupAllBooksAsync(books);
|
||||
}
|
||||
|
||||
private void BackupAllBooks(IEnumerable<LibraryBook> books)
|
||||
private async Task BackupAllBooksAsync(IEnumerable<LibraryBook> books)
|
||||
{
|
||||
try
|
||||
{
|
||||
var unliberated = books.UnLiberated().ToArray();
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
if (ProcessQueue.QueueDownloadDecrypt(unliberated))
|
||||
setQueueCollapseState(false);
|
||||
});
|
||||
if (await ProcessQueue.QueueDownloadDecryptAsync(unliberated))
|
||||
setQueueCollapseState(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ partial class MainVM
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ProcessQueue.QueueDownloadDecrypt(libraryBooks, config))
|
||||
if (await ProcessQueue.QueueDownloadDecryptAsync(libraryBooks, config))
|
||||
setQueueCollapseState(false);
|
||||
else if (libraryBooks.Count == 1 && libraryBooks[0].Book.AudioExists)
|
||||
{
|
||||
@@ -53,13 +53,13 @@ partial class MainVM
|
||||
}
|
||||
}
|
||||
|
||||
public void LiberateSeriesClicked(SeriesEntry series)
|
||||
public async void LiberateSeriesClicked(SeriesEntry series)
|
||||
{
|
||||
try
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin backing up all {series} episodes", series.LibraryBook);
|
||||
|
||||
if (ProcessQueue.QueueDownloadDecrypt(series.Children.Select(c => c.LibraryBook).UnLiberated().ToArray()))
|
||||
if (await ProcessQueue.QueueDownloadDecryptAsync(series.Children.Select(c => c.LibraryBook).UnLiberated().ToArray()))
|
||||
setQueueCollapseState(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -68,11 +68,11 @@ partial class MainVM
|
||||
=> await setLiberatedVisibleMenuItemAsync();
|
||||
|
||||
|
||||
public void LiberateVisible()
|
||||
public async void LiberateVisible()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ProcessQueue.QueueDownloadDecrypt(ProductsDisplay.GetVisibleBookEntries().UnLiberated().ToArray()))
|
||||
if (await ProcessQueue.QueueDownloadDecryptAsync(ProductsDisplay.GetVisibleBookEntries().UnLiberated().ToArray()))
|
||||
setQueueCollapseState(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
145
Source/LibationFileManager/DiskSpaceHelper.cs
Normal file
145
Source/LibationFileManager/DiskSpaceHelper.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationFileManager;
|
||||
|
||||
/// <summary>
|
||||
/// Detects disk-full I/O failures and reports free space for Libation backup paths.
|
||||
/// Preflight uses <see cref="DriveInfo"/> when available; runtime detection uses actual write failures
|
||||
/// (works even when free space cannot be queried, e.g. some UNC/SMB shares).
|
||||
/// </summary>
|
||||
public static class DiskSpaceHelper
|
||||
{
|
||||
/// <summary>Conservative per-title estimate (download + decrypt temp + final file) for bulk preflight.</summary>
|
||||
public const long EstimatedBytesPerAudiobookBackup = 400_000_000L;
|
||||
|
||||
/// <summary>Below this free space on a relevant drive, bulk backup is blocked (no Continue).</summary>
|
||||
public const long CriticalFreeBytes = 100_000_000L;
|
||||
|
||||
private const int HResultDiskFull = unchecked((int)0x80070070);
|
||||
|
||||
public static bool IsDiskFullException(Exception? ex)
|
||||
{
|
||||
for (var current = ex; current is not null; current = current.InnerException)
|
||||
{
|
||||
if (current is IOException && current.HResult == HResultDiskFull)
|
||||
return true;
|
||||
|
||||
if (ErrorMessageIndicatesDiskFull(current.Message))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches Windows-style disk-full text from logs and StatusHandler errors.
|
||||
/// Does not cover quota-specific wording from some NAS/cloud providers; those fall through to normal retry UI.
|
||||
/// </summary>
|
||||
public static bool ErrorMessageIndicatesDiskFull(string? message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
return false;
|
||||
|
||||
return message.Contains("not enough space on the disk", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("disk was full", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("there is not enough space on the disk", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns free bytes for the volume containing <paramref name="path"/>, or null if unknown.
|
||||
/// Null means preflight cannot warn/block on that root (writable shares with no capacity API, offline drive, bad path).
|
||||
/// On Windows, <see cref="Path.GetPathRoot"/> yields drive letters (C:\) or UNC roots (\\server\share\).
|
||||
/// </summary>
|
||||
public static long? TryGetAvailableFreeBytes(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var fullPath = Path.GetFullPath(path);
|
||||
var root = Path.GetPathRoot(fullPath);
|
||||
if (string.IsNullOrWhiteSpace(root))
|
||||
return null;
|
||||
|
||||
var drive = new DriveInfo(root);
|
||||
// IsReady is false for disconnected network drives; AvailableFreeSpace may be wrong on some NAS reporting.
|
||||
return drive.IsReady ? drive.AvailableFreeSpace : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// DriveInfo can throw for invalid roots; treat as unknown rather than failing backup setup.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyList<BackupDriveSpace> GetBackupDriveSpaces(Configuration config, int bookCount)
|
||||
{
|
||||
var requiredBytes = Math.Max(0, bookCount) * EstimatedBytesPerAudiobookBackup;
|
||||
var pathsByRoot = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
void addPath(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return;
|
||||
|
||||
string fullPath;
|
||||
try
|
||||
{
|
||||
fullPath = Path.GetFullPath(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var root = Path.GetPathRoot(fullPath);
|
||||
if (string.IsNullOrWhiteSpace(root))
|
||||
return;
|
||||
|
||||
// Same physical share via Z: vs \\server\share appears as two roots; each gets the full estimate (conservative).
|
||||
if (!pathsByRoot.TryGetValue(root, out var list))
|
||||
{
|
||||
list = [];
|
||||
pathsByRoot[root] = list;
|
||||
}
|
||||
|
||||
if (!list.Contains(fullPath, StringComparer.OrdinalIgnoreCase))
|
||||
list.Add(fullPath);
|
||||
}
|
||||
|
||||
addPath(config.Books?.Path);
|
||||
addPath(config.InProgress);
|
||||
|
||||
return pathsByRoot
|
||||
.Select(kvp => new BackupDriveSpace(
|
||||
kvp.Key,
|
||||
kvp.Value,
|
||||
TryGetAvailableFreeBytes(kvp.Key),
|
||||
requiredBytes))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True when every root is unknown or has enough reported space. All-unknown => no preflight dialog.
|
||||
/// </summary>
|
||||
public static bool HasSufficientSpaceForBulkBackup(IReadOnlyList<BackupDriveSpace> drives)
|
||||
=> drives.All(d => d.AvailableBytes is null || d.AvailableBytes >= d.RequiredBytes);
|
||||
|
||||
/// <summary>
|
||||
/// Only applies when free space was read successfully; unknown (null) never hard-blocks.
|
||||
/// </summary>
|
||||
public static bool AnyDriveCriticallyLow(IReadOnlyList<BackupDriveSpace> drives)
|
||||
=> drives.Any(d => d.AvailableBytes is not null && d.AvailableBytes < CriticalFreeBytes);
|
||||
|
||||
public readonly record struct BackupDriveSpace(
|
||||
/// <summary>Path root from <see cref="Path.GetPathRoot"/> (e.g. C:\ or \\nas\library\).</summary>
|
||||
string DriveRoot,
|
||||
IReadOnlyList<string> Paths,
|
||||
/// <summary>Null when <see cref="TryGetAvailableFreeBytes"/> could not query this root.</summary>
|
||||
long? AvailableBytes,
|
||||
long RequiredBytes);
|
||||
}
|
||||
@@ -172,7 +172,9 @@ public static class FilePathCache
|
||||
catch (IOException ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"Error saving {FILENAME_V2}");
|
||||
throw;
|
||||
// Keep in-memory cache; rethrow other I/O errors (permissions, network drop). Disk full must not take down the UI.
|
||||
if (!DiskSpaceHelper.IsDiskFullException(ex))
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
Source/LibationUiBase/DiskFullUserMessage.cs
Normal file
67
Source/LibationUiBase/DiskFullUserMessage.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using LibationFileManager;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LibationUiBase;
|
||||
|
||||
/// <summary>
|
||||
/// User-facing copy when backups fail or are blocked due to insufficient disk space.
|
||||
/// </summary>
|
||||
public static class DiskFullUserMessage
|
||||
{
|
||||
public const string DialogCaption = "Not enough disk space";
|
||||
|
||||
public static string BuildQueueStoppedBody()
|
||||
=> """
|
||||
Libation stopped the backup queue because the disk ran out of free space.
|
||||
|
||||
Download and decrypt use temporary files under your "In progress" folder, then write finished audiobooks to your Books location. Both need enough free space on their drives.
|
||||
|
||||
Free disk space (or move Books / In progress to a larger drive in Settings), delete partial files under your In progress folder if needed, then retry your backups in smaller batches.
|
||||
""";
|
||||
|
||||
public static string BuildPreflightBlockedBody(IReadOnlyList<DiskSpaceHelper.BackupDriveSpace> drives, int bookCount)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"You are about to back up {bookCount} books, but a drive Libation uses does not have enough free space to continue safely.");
|
||||
sb.AppendLine();
|
||||
AppendDriveLines(sb, drives);
|
||||
sb.AppendLine();
|
||||
sb.Append("Free space or change the Books and In progress locations in Settings, then try again.");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string BuildPreflightWarningBody(IReadOnlyList<DiskSpaceHelper.BackupDriveSpace> drives, int bookCount)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"You are about to back up {bookCount} books. Libation estimates you may need on the order of {FormatBytes(bookCount * DiskSpaceHelper.EstimatedBytesPerAudiobookBackup)} total, plus extra room for temporary files during each download.");
|
||||
sb.AppendLine();
|
||||
AppendDriveLines(sb, drives);
|
||||
sb.AppendLine();
|
||||
sb.Append("Continue anyway?");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void AppendDriveLines(StringBuilder sb, IReadOnlyList<DiskSpaceHelper.BackupDriveSpace> drives)
|
||||
{
|
||||
foreach (var drive in drives)
|
||||
{
|
||||
// "unknown" when DriveInfo could not report space (typical for some network paths); user may still have chosen Continue.
|
||||
var free = drive.AvailableBytes is null ? "unknown" : FormatBytes(drive.AvailableBytes.Value);
|
||||
var needed = FormatBytes(drive.RequiredBytes);
|
||||
sb.AppendLine($"{drive.DriveRoot} Free: {free} Estimated needed: {needed}");
|
||||
foreach (var path in drive.Paths)
|
||||
sb.AppendLine($" {path}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatBytes(long bytes)
|
||||
{
|
||||
const long gb = 1024L * 1024 * 1024;
|
||||
if (bytes >= gb)
|
||||
return $"{bytes / (double)gb:F1} GB";
|
||||
const long mb = 1024 * 1024;
|
||||
return $"{bytes / (double)mb:F0} MB";
|
||||
}
|
||||
}
|
||||
48
Source/LibationUiBase/DiskSpaceBackupPreflight.cs
Normal file
48
Source/LibationUiBase/DiskSpaceBackupPreflight.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using LibationFileManager;
|
||||
using LibationUiBase.Forms;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationUiBase;
|
||||
|
||||
/// <summary>
|
||||
/// Optional bulk-backup gate before titles are queued. Skipped for single-book backups.
|
||||
/// Network paths that do not report free space pass through with no dialog; failures are handled at download time.
|
||||
/// </summary>
|
||||
public static class DiskSpaceBackupPreflight
|
||||
{
|
||||
private const int BulkBackupBookThreshold = 2;
|
||||
|
||||
public static async Task<bool> ConfirmBulkBackupAsync(int bookCount, Configuration config)
|
||||
{
|
||||
if (bookCount < BulkBackupBookThreshold)
|
||||
return true;
|
||||
|
||||
var drives = DiskSpaceHelper.GetBackupDriveSpaces(config, bookCount);
|
||||
|
||||
// All roots unknown, or all have enough reported space: queue without prompting.
|
||||
if (DiskSpaceHelper.HasSufficientSpaceForBulkBackup(drives))
|
||||
return true;
|
||||
|
||||
// Known space below CriticalFreeBytes: do not offer Continue (avoids starting a huge queue on a full local disk).
|
||||
if (DiskSpaceHelper.AnyDriveCriticallyLow(drives))
|
||||
{
|
||||
await MessageBoxBase.Show(
|
||||
DiskFullUserMessage.BuildPreflightBlockedBody(drives, bookCount),
|
||||
DiskFullUserMessage.DialogCaption,
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// At least one root reported space below estimate but above critical (or mixed known/unknown with a shortfall).
|
||||
var result = await MessageBoxBase.Show(
|
||||
DiskFullUserMessage.BuildPreflightWarningBody(drives, bookCount),
|
||||
DiskFullUserMessage.DialogCaption,
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning,
|
||||
MessageBoxDefaultButton.Button2);
|
||||
|
||||
return result == DialogResult.Yes;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,9 @@ public enum ProcessBookResult
|
||||
FailedSkip,
|
||||
FailedAbort,
|
||||
LicenseDenied,
|
||||
LicenseDeniedPossibleOutage
|
||||
LicenseDeniedPossibleOutage,
|
||||
/// <summary>Volume full on write; queue should stop (see ProcessQueueViewModel queue loop).</summary>
|
||||
DiskFull
|
||||
}
|
||||
|
||||
public enum ProcessBookStatus
|
||||
@@ -69,6 +71,7 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
(ProcessBookResult.LicenseDenied, true) => "License denied (Plus; often temporary)",
|
||||
(ProcessBookResult.LicenseDenied, false) => "License Denied",
|
||||
(ProcessBookResult.LicenseDeniedPossibleOutage, _) => "Possible Service Interruption",
|
||||
(ProcessBookResult.DiskFull, _) => "Disk full, queue stopped",
|
||||
_ => Status.ToString(),
|
||||
};
|
||||
|
||||
@@ -152,6 +155,13 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
{
|
||||
foreach (var errorMessage in statusHandler.Errors)
|
||||
LogError($"{procName}: {errorMessage}");
|
||||
|
||||
// Prefer disk-full detection over generic retry; avoids treating truncated .aaxc as a normal failure.
|
||||
if (statusHandler.Errors.Any(DiskSpaceHelper.ErrorMessageIndicatesDiskFull))
|
||||
{
|
||||
LogInfo($"{procName}: Disk is full. Free space or change Books / In progress in Settings. - {LibraryBook.Book}");
|
||||
result = ProcessBookResult.DiskFull;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ContentLicenseDeniedException ldex)
|
||||
@@ -173,6 +183,13 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
result = ProcessBookResult.LicenseDenied;
|
||||
}
|
||||
}
|
||||
// HRESULT 0x80070070 / known messages from the OS when a volume is full (including many SMB shares).
|
||||
catch (Exception ex) when (DiskSpaceHelper.IsDiskFullException(ex))
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Disk full during {ProcName} for {{@Book}}", procName, LibraryBook.LogFriendly());
|
||||
LogInfo($"{procName}: Disk is full. Free space or change Books / In progress in Settings. - {LibraryBook.Book}");
|
||||
result = ProcessBookResult.DiskFull;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"Unhandled exception in {procName} for {{@Book}}", LibraryBook.LogFriendly());
|
||||
@@ -180,6 +197,7 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
}
|
||||
finally
|
||||
{
|
||||
// DiskFull skips the per-book Abort/Retry/Ignore dialog; the queue shows one disk-full message instead.
|
||||
if (result == ProcessBookResult.None)
|
||||
result = await GetFailureActionAsync(LibraryBook);
|
||||
|
||||
@@ -345,6 +363,9 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
{
|
||||
foreach (var errorMessage in result.Errors.Where(e => e != "Validation failed"))
|
||||
LogError(errorMessage);
|
||||
|
||||
if (result.Errors.Any(DiskSpaceHelper.ErrorMessageIndicatesDiskFull))
|
||||
LogInfo("Disk is full. Free space or change Books / In progress in Settings.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,8 +73,13 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
|
||||
private void Queue_CompletedCountChanged(object? sender, int e)
|
||||
{
|
||||
int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.FailedRetry or ProcessBookResult.ValidationFail);
|
||||
int completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success);
|
||||
var errCount = Queue.Completed.Count(p => p.Result
|
||||
is ProcessBookResult.FailedAbort
|
||||
or ProcessBookResult.FailedSkip
|
||||
or ProcessBookResult.FailedRetry
|
||||
or ProcessBookResult.ValidationFail
|
||||
or ProcessBookResult.DiskFull);
|
||||
var completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success);
|
||||
|
||||
ErrorCount = errCount;
|
||||
CompletedCount = completeCount;
|
||||
@@ -127,7 +132,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool QueueDownloadDecrypt(IList<LibraryBook> libraryBooks, Configuration? config = null)
|
||||
public async Task<bool> QueueDownloadDecryptAsync(IList<LibraryBook> libraryBooks, Configuration? config = null)
|
||||
{
|
||||
config ??= Configuration.Instance;
|
||||
if (!IsBooksDirectoryValid(config))
|
||||
@@ -182,6 +187,10 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
|
||||
if (toLiberate.Length > 0)
|
||||
{
|
||||
// May no-op when free space is unknown (common on UNC); see DiskSpaceBackupPreflight.
|
||||
if (!await DiskSpaceBackupPreflight.ConfirmBulkBackupAsync(toLiberate.Length, config))
|
||||
return false;
|
||||
|
||||
Serilog.Log.Logger.Information("Begin backup of {count} library books", toLiberate.Length);
|
||||
AddDownloadDecrypt(toLiberate, config);
|
||||
return true;
|
||||
@@ -299,6 +308,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
ProgressBarVisible = true;
|
||||
var startingTime = DateTime.Now;
|
||||
bool shownLicenseGuidanceMessage = false;
|
||||
bool shownDiskFullMessage = false;
|
||||
|
||||
using var counterTimer = new System.Threading.Timer(_ => RunningTime = timeToStr(DateTime.Now - startingTime), null, 0, 500);
|
||||
|
||||
@@ -321,6 +331,20 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
Queue.ClearCurrent();
|
||||
else if (result == ProcessBookResult.FailedAbort)
|
||||
Queue.ClearQueue();
|
||||
// Stop the whole queue on first real disk-full write (local or network); do not retry hundreds of titles.
|
||||
else if (result == ProcessBookResult.DiskFull)
|
||||
{
|
||||
if (!shownDiskFullMessage)
|
||||
{
|
||||
await MessageBoxBase.Show(
|
||||
DiskFullUserMessage.BuildQueueStoppedBody(),
|
||||
DiskFullUserMessage.DialogCaption,
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning);
|
||||
shownDiskFullMessage = true;
|
||||
}
|
||||
Queue.ClearQueue();
|
||||
}
|
||||
else if (result == ProcessBookResult.FailedSkip)
|
||||
await nextBook.LibraryBook.UpdateBookStatusAsync(LiberatedStatus.Error);
|
||||
else if (!shownLicenseGuidanceMessage
|
||||
|
||||
@@ -19,16 +19,13 @@ public partial class Form1
|
||||
BackupAllBooks(library);
|
||||
}
|
||||
|
||||
private void BackupAllBooks(IEnumerable<LibraryBook> books)
|
||||
private async void BackupAllBooks(IEnumerable<LibraryBook> books)
|
||||
{
|
||||
try
|
||||
{
|
||||
var unliberated = books.UnLiberated().ToArray();
|
||||
Invoke(() =>
|
||||
{
|
||||
if (processBookQueue1.ViewModel.QueueDownloadDecrypt(unliberated))
|
||||
SetQueueCollapseState(false);
|
||||
});
|
||||
if (await processBookQueue1.ViewModel.QueueDownloadDecryptAsync(unliberated))
|
||||
SetQueueCollapseState(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -23,11 +23,11 @@ public partial class Form1
|
||||
this.Width = width;
|
||||
}
|
||||
|
||||
private void ProductsDisplay_LiberateClicked(object sender, System.Collections.Generic.IList<LibraryBook> libraryBooks, Configuration config)
|
||||
private async void ProductsDisplay_LiberateClicked(object sender, System.Collections.Generic.IList<LibraryBook> libraryBooks, Configuration config)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (processBookQueue1.ViewModel.QueueDownloadDecrypt(libraryBooks, config))
|
||||
if (await processBookQueue1.ViewModel.QueueDownloadDecryptAsync(libraryBooks, config))
|
||||
SetQueueCollapseState(false);
|
||||
else if (libraryBooks.Count == 1 && libraryBooks[0].Book.AudioExists)
|
||||
{
|
||||
@@ -46,13 +46,13 @@ public partial class Form1
|
||||
}
|
||||
}
|
||||
|
||||
private void ProductsDisplay_LiberateSeriesClicked(object sender, SeriesEntry series)
|
||||
private async void ProductsDisplay_LiberateSeriesClicked(object sender, SeriesEntry series)
|
||||
{
|
||||
try
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin backing up all {series} episodes", series.LibraryBook);
|
||||
|
||||
if (processBookQueue1.ViewModel.QueueDownloadDecrypt(series.Children.Select(c => c.LibraryBook).UnLiberated().ToArray()))
|
||||
if (await processBookQueue1.ViewModel.QueueDownloadDecryptAsync(series.Children.Select(c => c.LibraryBook).UnLiberated().ToArray()))
|
||||
SetQueueCollapseState(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -54,11 +54,11 @@ public partial class Form1
|
||||
});
|
||||
}
|
||||
|
||||
private void liberateVisible(object sender, EventArgs e)
|
||||
private async void liberateVisible(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (processBookQueue1.ViewModel.QueueDownloadDecrypt(productsDisplay.GetVisible().UnLiberated().ToArray()))
|
||||
if (await processBookQueue1.ViewModel.QueueDownloadDecryptAsync(productsDisplay.GetVisible().UnLiberated().ToArray()))
|
||||
SetQueueCollapseState(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using LibationFileManager;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LibationFileManager.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class DiskSpaceHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void IsDiskFullException_detects_message()
|
||||
{
|
||||
var ex = new IOException("There is not enough space on the disk. : 'C:\\temp\\x.aaxc'.");
|
||||
Assert.IsTrue(DiskSpaceHelper.IsDiskFullException(ex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsDiskFullException_detects_message_in_aggregate()
|
||||
{
|
||||
var inner = new IOException("Failed to create file because the disk was full.");
|
||||
var ex = new AggregateException(inner);
|
||||
Assert.IsTrue(DiskSpaceHelper.IsDiskFullException(ex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ErrorMessageIndicatesDiskFull_matches_common_phrases()
|
||||
{
|
||||
Assert.IsTrue(DiskSpaceHelper.ErrorMessageIndicatesDiskFull("There is not enough space on the disk. : 'C:\\temp\\x.aaxc'."));
|
||||
Assert.IsFalse(DiskSpaceHelper.ErrorMessageIndicatesDiskFull("Unable to read beyond the end of the stream."));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user