mirror of
https://github.com/rmcrackan/Libation.git
synced 2025-12-23 22:17:52 -05:00
Add "Download split by chapters" context menu item (#1436)
All processables are now created with an instance of Configuration, and they use that instance's settings. Added Configuration.CreateEphemeralCopy() to clone Configuration without persistence.
This commit is contained in:
@@ -16,9 +16,10 @@ namespace FileLiberator
|
||||
/// Path: directory nested inside of Books directory
|
||||
/// File name: n/a
|
||||
/// </summary>
|
||||
public static string GetDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook)
|
||||
public static string GetDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook, Configuration config = null)
|
||||
{
|
||||
if (libraryBook.Book.IsEpisodeChild() && Configuration.Instance.SavePodcastsToParentFolder)
|
||||
config ??= Configuration.Instance;
|
||||
if (libraryBook.Book.IsEpisodeChild() && config.SavePodcastsToParentFolder)
|
||||
{
|
||||
var series = libraryBook.Book.SeriesLink.SingleOrDefault();
|
||||
if (series is not null)
|
||||
|
||||
@@ -13,7 +13,7 @@ using LibationFileManager;
|
||||
|
||||
namespace FileLiberator
|
||||
{
|
||||
public class ConvertToMp3 : AudioDecodable
|
||||
public class ConvertToMp3 : AudioDecodable, IProcessable<ConvertToMp3>
|
||||
{
|
||||
public override string Name => "Convert to Mp3";
|
||||
private Mp4Operation Mp4Operation;
|
||||
@@ -72,15 +72,14 @@ namespace FileLiberator
|
||||
OnNarratorsDiscovered(m4bBook.AppleTags.Narrator);
|
||||
OnCoverImageDiscovered(m4bBook.AppleTags.Cover);
|
||||
|
||||
var config = Configuration.Instance;
|
||||
var lameConfig = DownloadOptions.GetLameOptions(config);
|
||||
var lameConfig = DownloadOptions.GetLameOptions(Configuration);
|
||||
var chapters = m4bBook.GetChaptersFromMetadata();
|
||||
//Finishing configuring lame encoder.
|
||||
AaxDecrypter.MpegUtil.ConfigureLameOptions(
|
||||
m4bBook,
|
||||
lameConfig,
|
||||
config.LameDownsampleMono,
|
||||
config.LameMatchSourceBR,
|
||||
Configuration.LameDownsampleMono,
|
||||
Configuration.LameMatchSourceBR,
|
||||
chapters);
|
||||
|
||||
if (m4bBook.AppleTags.Tracks is (int trackNum, int trackCount))
|
||||
@@ -108,9 +107,9 @@ namespace FileLiberator
|
||||
= FileUtility.SaferMoveToValidPath(
|
||||
tempPath,
|
||||
entry.proposedMp3Path,
|
||||
Configuration.Instance.ReplacementCharacters,
|
||||
Configuration.ReplacementCharacters,
|
||||
extension: "mp3",
|
||||
Configuration.Instance.OverwriteExisting);
|
||||
Configuration.OverwriteExisting);
|
||||
|
||||
SetFileTime(libraryBook, realMp3Path);
|
||||
SetDirectoryTime(libraryBook, Path.GetDirectoryName(realMp3Path));
|
||||
@@ -169,5 +168,7 @@ namespace FileLiberator
|
||||
TotalBytesToReceive = totalInputSize
|
||||
});
|
||||
}
|
||||
public static ConvertToMp3 Create(Configuration config) => new() { Configuration = config };
|
||||
private ConvertToMp3() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ using System.Threading.Tasks;
|
||||
#nullable enable
|
||||
namespace FileLiberator
|
||||
{
|
||||
public class DownloadDecryptBook : AudioDecodable
|
||||
public class DownloadDecryptBook : AudioDecodable, IProcessable<DownloadDecryptBook>
|
||||
{
|
||||
public override string Name => "Download & Decrypt";
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
@@ -50,8 +50,8 @@ namespace FileLiberator
|
||||
|
||||
var api = await libraryBook.GetApiAsync();
|
||||
|
||||
LicenseInfo ??= await DownloadOptions.GetDownloadLicenseAsync(api, libraryBook, Configuration.Instance, cancellationToken);
|
||||
using var downloadOptions = DownloadOptions.BuildDownloadOptions(libraryBook, Configuration.Instance, LicenseInfo);
|
||||
LicenseInfo ??= await DownloadOptions.GetDownloadLicenseAsync(api, libraryBook, Configuration, cancellationToken);
|
||||
using var downloadOptions = DownloadOptions.BuildDownloadOptions(libraryBook, Configuration, LicenseInfo);
|
||||
var result = await DownloadAudiobookAsync(api, downloadOptions, cancellationToken);
|
||||
|
||||
if (!result.Success || getFirstAudioFile(result.ResultFiles) is not TempFile audioFile)
|
||||
@@ -62,7 +62,7 @@ namespace FileLiberator
|
||||
return new StatusHandler { "Decrypt failed" };
|
||||
}
|
||||
|
||||
if (Configuration.Instance.RetainAaxFile)
|
||||
if (Configuration.RetainAaxFile)
|
||||
{
|
||||
//Add the cached aaxc and key files to the entries list to be moved to the Books directory.
|
||||
result.ResultFiles.AddRange(getAaxcFiles(result.CacheFiles));
|
||||
@@ -256,7 +256,7 @@ namespace FileLiberator
|
||||
|
||||
private void AaxcDownloader_RetrievedCoverArt(object? sender, byte[]? e)
|
||||
{
|
||||
if (Configuration.Instance.AllowLibationFixup && sender is AaxcDownloadConvertBase downloader)
|
||||
if (Configuration.AllowLibationFixup && sender is AaxcDownloadConvertBase downloader)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -345,15 +345,15 @@ namespace FileLiberator
|
||||
destinationDir,
|
||||
entry.Extension,
|
||||
entry.PartProperties,
|
||||
Configuration.Instance.OverwriteExisting);
|
||||
Configuration.OverwriteExisting);
|
||||
|
||||
var realDest
|
||||
= FileUtility.SaferMoveToValidPath(
|
||||
entry.FilePath,
|
||||
destFileName,
|
||||
Configuration.Instance.ReplacementCharacters,
|
||||
Configuration.ReplacementCharacters,
|
||||
entry.Extension,
|
||||
Configuration.Instance.OverwriteExisting);
|
||||
Configuration.OverwriteExisting);
|
||||
|
||||
#region File Move Progress
|
||||
totalBytesMoved += new FileInfo(realDest).Length;
|
||||
@@ -403,7 +403,7 @@ namespace FileLiberator
|
||||
options.LibraryBook,
|
||||
destinationDir,
|
||||
extension: ".jpg",
|
||||
returnFirstExisting: Configuration.Instance.OverwriteExisting);
|
||||
returnFirstExisting: Configuration.OverwriteExisting);
|
||||
|
||||
if (File.Exists(coverPath))
|
||||
FileUtility.SaferDelete(coverPath);
|
||||
@@ -440,7 +440,7 @@ namespace FileLiberator
|
||||
options.LibraryBook,
|
||||
destinationDir,
|
||||
extension: formatExtension,
|
||||
returnFirstExisting: Configuration.Instance.OverwriteExisting);
|
||||
returnFirstExisting: Configuration.OverwriteExisting);
|
||||
|
||||
if (File.Exists(recordsPath))
|
||||
FileUtility.SaferDelete(recordsPath);
|
||||
@@ -487,7 +487,7 @@ namespace FileLiberator
|
||||
options.LibraryBook,
|
||||
destinationDir,
|
||||
extension: ".metadata.json",
|
||||
returnFirstExisting: Configuration.Instance.OverwriteExisting);
|
||||
returnFirstExisting: Configuration.OverwriteExisting);
|
||||
|
||||
if (File.Exists(metadataPath))
|
||||
FileUtility.SaferDelete(metadataPath);
|
||||
@@ -512,10 +512,10 @@ namespace FileLiberator
|
||||
#endregion
|
||||
|
||||
#region Macros
|
||||
private static string getDestinationDirectory(LibraryBook libraryBook)
|
||||
private string getDestinationDirectory(LibraryBook libraryBook)
|
||||
{
|
||||
Serilog.Log.Verbose("Getting destination directory for {@Book}", libraryBook.LogFriendly());
|
||||
var destinationDir = AudibleFileStorage.Audio.GetDestinationDirectory(libraryBook);
|
||||
var destinationDir = AudibleFileStorage.Audio.GetDestinationDirectory(libraryBook, Configuration);
|
||||
Serilog.Log.Verbose("Got destination directory for {@Book}. {@Directory}", libraryBook.LogFriendly(), destinationDir);
|
||||
if (!Directory.Exists(destinationDir))
|
||||
{
|
||||
@@ -533,5 +533,8 @@ namespace FileLiberator
|
||||
private static IEnumerable<TempFile> getAaxcFiles(IEnumerable<TempFile> entries)
|
||||
=> entries.Where(f => File.Exists(f.FilePath) && (getFileType(f) is FileType.AAXC || f.Extension.Equals(".key", StringComparison.OrdinalIgnoreCase)));
|
||||
#endregion
|
||||
|
||||
public static DownloadDecryptBook Create(Configuration config) => new() { Configuration = config };
|
||||
private DownloadDecryptBook() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ using LibationFileManager;
|
||||
|
||||
namespace FileLiberator
|
||||
{
|
||||
public class DownloadPdf : Processable
|
||||
public class DownloadPdf : Processable, IProcessable<DownloadPdf>
|
||||
{
|
||||
public override string Name => "Download Pdf";
|
||||
public override bool Validate(LibraryBook libraryBook)
|
||||
@@ -89,5 +89,8 @@ namespace FileLiberator
|
||||
=> !File.Exists(actualDownloadedFilePath)
|
||||
? new StatusHandler { "Downloaded PDF cannot be found" }
|
||||
: new StatusHandler();
|
||||
|
||||
public static DownloadPdf Create(Configuration config) => new() { Configuration = config };
|
||||
private DownloadPdf() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,24 +9,36 @@ using Dinah.Core.ErrorHandling;
|
||||
using Dinah.Core.Net.Http;
|
||||
using LibationFileManager;
|
||||
|
||||
#nullable enable
|
||||
namespace FileLiberator
|
||||
{
|
||||
public abstract class Processable
|
||||
public interface IProcessable<T> where T : IProcessable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new instance of the Processable which uses a specific Configuration
|
||||
/// </summary>
|
||||
/// <param name="config">The <see cref="Configuration"/> this <typeparamref name="T"/> will use</param>
|
||||
static abstract T Create(Configuration config);
|
||||
}
|
||||
public abstract class Processable
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
public event EventHandler<LibraryBook> Begin;
|
||||
public event EventHandler<LibraryBook>? Begin;
|
||||
|
||||
/// <summary>General string message to display. DON'T rely on this for success, failure, or control logic</summary>
|
||||
public event EventHandler<string> StatusUpdate;
|
||||
public event EventHandler<string>? StatusUpdate;
|
||||
/// <summary>Fired when a file is successfully saved to disk</summary>
|
||||
public event EventHandler<(string id, string path)> FileCreated;
|
||||
public event EventHandler<DownloadProgress> StreamingProgressChanged;
|
||||
public event EventHandler<TimeSpan> StreamingTimeRemaining;
|
||||
public event EventHandler<(string id, string path)>? FileCreated;
|
||||
public event EventHandler<DownloadProgress>? StreamingProgressChanged;
|
||||
public event EventHandler<TimeSpan>? StreamingTimeRemaining;
|
||||
|
||||
public event EventHandler<LibraryBook> Completed;
|
||||
public event EventHandler<LibraryBook>? Completed;
|
||||
|
||||
/// <returns>True == Valid</returns>
|
||||
public abstract bool Validate(LibraryBook libraryBook);
|
||||
public required Configuration Configuration{ get; init; }
|
||||
protected Processable() { }
|
||||
|
||||
/// <returns>True == Valid</returns>
|
||||
public abstract bool Validate(LibraryBook libraryBook);
|
||||
|
||||
/// <returns>True == success</returns>
|
||||
public abstract Task<StatusHandler> ProcessAsync(LibraryBook libraryBook);
|
||||
@@ -35,7 +47,7 @@ namespace FileLiberator
|
||||
public IEnumerable<LibraryBook> GetValidLibraryBooks(IEnumerable<LibraryBook> library)
|
||||
=> library.Where(libraryBook =>
|
||||
Validate(libraryBook)
|
||||
&& (!libraryBook.Book.IsEpisodeChild() || Configuration.Instance.DownloadEpisodes)
|
||||
&& (!libraryBook.Book.IsEpisodeChild() || Configuration.DownloadEpisodes)
|
||||
);
|
||||
|
||||
public async Task<StatusHandler> ProcessSingleAsync(LibraryBook libraryBook, bool validate)
|
||||
@@ -86,12 +98,12 @@ namespace FileLiberator
|
||||
|
||||
protected void OnStreamingProgressChanged(DownloadProgress progress)
|
||||
=> OnStreamingProgressChanged(null, progress);
|
||||
protected void OnStreamingProgressChanged(object _, DownloadProgress progress)
|
||||
protected void OnStreamingProgressChanged(object? _, DownloadProgress progress)
|
||||
=> StreamingProgressChanged?.Invoke(this, progress);
|
||||
|
||||
protected void OnStreamingTimeRemaining(TimeSpan timeRemaining)
|
||||
=> OnStreamingTimeRemaining(null, timeRemaining);
|
||||
protected void OnStreamingTimeRemaining(object _, TimeSpan timeRemaining)
|
||||
protected void OnStreamingTimeRemaining(object? _, TimeSpan timeRemaining)
|
||||
=> StreamingTimeRemaining?.Invoke(this, timeRemaining);
|
||||
|
||||
protected void OnCompleted(LibraryBook libraryBook)
|
||||
@@ -100,17 +112,17 @@ namespace FileLiberator
|
||||
Completed?.Invoke(this, libraryBook);
|
||||
}
|
||||
|
||||
protected static void SetFileTime(LibraryBook libraryBook, string file)
|
||||
protected void SetFileTime(LibraryBook libraryBook, string file)
|
||||
=> setFileSystemTime(libraryBook, new FileInfo(file));
|
||||
protected static void SetDirectoryTime(LibraryBook libraryBook, string file)
|
||||
protected void SetDirectoryTime(LibraryBook libraryBook, string file)
|
||||
=> setFileSystemTime(libraryBook, new DirectoryInfo(file));
|
||||
|
||||
private static void setFileSystemTime(LibraryBook libraryBook, FileSystemInfo fileInfo)
|
||||
private void setFileSystemTime(LibraryBook libraryBook, FileSystemInfo fileInfo)
|
||||
{
|
||||
if (!fileInfo.Exists) return;
|
||||
|
||||
fileInfo.CreationTimeUtc = getTimeValue(Configuration.Instance.CreationTime) ?? fileInfo.CreationTimeUtc;
|
||||
fileInfo.LastWriteTimeUtc = getTimeValue(Configuration.Instance.LastWriteTime) ?? fileInfo.LastWriteTimeUtc;
|
||||
fileInfo.CreationTimeUtc = getTimeValue(Configuration.CreationTime) ?? fileInfo.CreationTimeUtc;
|
||||
fileInfo.LastWriteTimeUtc = getTimeValue(Configuration.LastWriteTime) ?? fileInfo.LastWriteTimeUtc;
|
||||
|
||||
DateTime? getTimeValue(Configuration.DateTimeSource source) => source switch
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace FileManager;
|
||||
|
||||
public interface IJsonBackedDictionary
|
||||
{
|
||||
JObject GetJObject();
|
||||
bool Exists(string propertyName);
|
||||
string? GetString(string propertyName, string? defaultValue = null);
|
||||
T? GetNonString<T>(string propertyName, T? defaultValue = default);
|
||||
|
||||
@@ -273,5 +273,7 @@ namespace FileManager
|
||||
{
|
||||
File.WriteAllText(Filepath, "{}");
|
||||
}
|
||||
}
|
||||
|
||||
public JObject GetJObject() => readFile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using Avalonia.Controls;
|
||||
using DataLayer;
|
||||
using Dinah.Core.ErrorHandling;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase.ProcessQueue;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -32,11 +33,11 @@ public partial class ThemePreviewControl : UserControl
|
||||
MainVM.Configure_NonUI();
|
||||
}
|
||||
|
||||
QueuedBook = new ProcessBookViewModel(sampleEntries[0]) { Status = ProcessBookStatus.Queued };
|
||||
WorkingBook = new ProcessBookViewModel(sampleEntries[0]) { Status = ProcessBookStatus.Working };
|
||||
CompletedBook = new ProcessBookViewModel(sampleEntries[0]) { Status = ProcessBookStatus.Completed };
|
||||
CancelledBook = new ProcessBookViewModel(sampleEntries[0]) { Status = ProcessBookStatus.Cancelled };
|
||||
FailedBook = new ProcessBookViewModel(sampleEntries[0]) { Status = ProcessBookStatus.Failed };
|
||||
QueuedBook = new ProcessBookViewModel(sampleEntries[0], Configuration.Instance) { Status = ProcessBookStatus.Queued };
|
||||
WorkingBook = new ProcessBookViewModel(sampleEntries[0], Configuration.Instance) { Status = ProcessBookStatus.Working };
|
||||
CompletedBook = new ProcessBookViewModel(sampleEntries[0], Configuration.Instance) { Status = ProcessBookStatus.Completed };
|
||||
CancelledBook = new ProcessBookViewModel(sampleEntries[0], Configuration.Instance) { Status = ProcessBookStatus.Cancelled };
|
||||
FailedBook = new ProcessBookViewModel(sampleEntries[0], Configuration.Instance) { Status = ProcessBookStatus.Failed };
|
||||
|
||||
//Set the current processable so that the empty queue doesn't try to advance.
|
||||
QueuedBook.AddDownloadPdf();
|
||||
|
||||
@@ -32,13 +32,13 @@ namespace LibationAvalonia.ViewModels
|
||||
setQueueCollapseState(collapseState);
|
||||
}
|
||||
|
||||
public async void LiberateClicked(LibraryBook[] libraryBooks)
|
||||
public async void LiberateClicked(System.Collections.Generic.IList<LibraryBook> libraryBooks, Configuration config)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ProcessQueue.QueueDownloadDecrypt(libraryBooks))
|
||||
if (ProcessQueue.QueueDownloadDecrypt(libraryBooks, config))
|
||||
setQueueCollapseState(false);
|
||||
else if (libraryBooks.Length == 1 && libraryBooks[0].Book.AudioExists)
|
||||
else if (libraryBooks.Count == 1 && libraryBooks[0].Book.AudioExists)
|
||||
{
|
||||
// liberated: open explorer to file
|
||||
var filePath = AudibleFileStorage.Audio.GetPath(libraryBooks[0].Book.AudibleProductId);
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace LibationAvalonia.Views
|
||||
await ViewModel.BindToGridTask;
|
||||
}
|
||||
|
||||
public void ProductsDisplay_LiberateClicked(object _, LibraryBook[] libraryBook) => ViewModel.LiberateClicked(libraryBook);
|
||||
public void ProductsDisplay_LiberateClicked(object _, IList<LibraryBook> libraryBook, Configuration config) => ViewModel.LiberateClicked(libraryBook, config);
|
||||
public void ProductsDisplay_LiberateSeriesClicked(object _, SeriesEntry series) => ViewModel.LiberateSeriesClicked(series);
|
||||
public void ProductsDisplay_ConvertToMp3Clicked(object _, LibraryBook[] libraryBook) => ViewModel.ConvertToMp3Clicked(libraryBook);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace LibationAvalonia.Views
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
ViewModels.MainVM.Configure_NonUI();
|
||||
DataContext = new ProcessBookViewModel(MockLibraryBook.CreateBook());
|
||||
DataContext = new ProcessBookViewModel(MockLibraryBook.CreateBook(), LibationFileManager.Configuration.Instance);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,42 +38,42 @@ namespace LibationAvalonia.Views
|
||||
var trialBook = MockLibraryBook.CreateBook();
|
||||
List<ProcessBookViewModel> testList = new()
|
||||
{
|
||||
new ProcessBookViewModel(trialBook)
|
||||
new ProcessBookViewModel(trialBook, Configuration.Instance)
|
||||
{
|
||||
Result = ProcessBookResult.FailedAbort,
|
||||
Status = ProcessBookStatus.Failed,
|
||||
},
|
||||
new ProcessBookViewModel(trialBook)
|
||||
new ProcessBookViewModel(trialBook, Configuration.Instance)
|
||||
{
|
||||
Result = ProcessBookResult.FailedSkip,
|
||||
Status = ProcessBookStatus.Failed,
|
||||
},
|
||||
new ProcessBookViewModel(trialBook)
|
||||
new ProcessBookViewModel(trialBook, Configuration.Instance)
|
||||
{
|
||||
Result = ProcessBookResult.FailedRetry,
|
||||
Status = ProcessBookStatus.Failed,
|
||||
},
|
||||
new ProcessBookViewModel(trialBook)
|
||||
new ProcessBookViewModel(trialBook, Configuration.Instance)
|
||||
{
|
||||
Result = ProcessBookResult.ValidationFail,
|
||||
Status = ProcessBookStatus.Failed,
|
||||
},
|
||||
new ProcessBookViewModel(trialBook)
|
||||
new ProcessBookViewModel(trialBook, Configuration.Instance)
|
||||
{
|
||||
Result = ProcessBookResult.Cancelled,
|
||||
Status = ProcessBookStatus.Cancelled,
|
||||
},
|
||||
new ProcessBookViewModel(trialBook)
|
||||
new ProcessBookViewModel(trialBook, Configuration.Instance)
|
||||
{
|
||||
Result = ProcessBookResult.Success,
|
||||
Status = ProcessBookStatus.Completed,
|
||||
},
|
||||
new ProcessBookViewModel(trialBook)
|
||||
new ProcessBookViewModel(trialBook, Configuration.Instance)
|
||||
{
|
||||
Result = ProcessBookResult.None,
|
||||
Status = ProcessBookStatus.Working,
|
||||
},
|
||||
new ProcessBookViewModel(trialBook)
|
||||
new ProcessBookViewModel(trialBook, Configuration.Instance)
|
||||
{
|
||||
Result = ProcessBookResult.None,
|
||||
Status = ProcessBookStatus.Queued,
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace LibationAvalonia.Views
|
||||
{
|
||||
public partial class ProductsDisplay : UserControl
|
||||
{
|
||||
public event EventHandler<LibraryBook[]>? LiberateClicked;
|
||||
public event LiberateClickedHandler? LiberateClicked;
|
||||
public event EventHandler<SeriesEntry>? LiberateSeriesClicked;
|
||||
public event EventHandler<LibraryBook[]>? ConvertToMp3Clicked;
|
||||
public event EventHandler<LibraryBook>? TagsButtonClicked;
|
||||
@@ -298,10 +298,29 @@ namespace LibationAvalonia.Views
|
||||
args.ContextMenuItems.Add(new MenuItem
|
||||
{
|
||||
Header = ctx.DownloadSelectedText,
|
||||
Command = ReactiveCommand.Create(() => LiberateClicked?.Invoke(this, ctx.LibraryBookEntries.Select(e => e.LibraryBook).ToArray()))
|
||||
Command = ReactiveCommand.Create(() => LiberateClicked?.Invoke(this, ctx.LibraryBookEntries.Select(e => e.LibraryBook).ToArray(), Configuration.Instance))
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region Download split by chapters
|
||||
if (entries.Length == 1 && entries[0] is LibraryBookEntry entry3_a)
|
||||
{
|
||||
args.ContextMenuItems.Add(new MenuItem()
|
||||
{
|
||||
Header = ctx.DownloadAsChapters,
|
||||
IsEnabled = ctx.DownloadAsChaptersEnabled,
|
||||
Command = ReactiveCommand.Create(() =>
|
||||
{
|
||||
var config = Configuration.Instance.CreateEphemeralCopy();
|
||||
config.AllowLibationFixup = config.SplitFilesByChapter = true;
|
||||
var books = ctx.LibraryBookEntries.Select(e => e.LibraryBook).Where(lb => lb.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Error).ToList();
|
||||
//No need to persist BookStatus changes. They only needs to last long for the files to start downloading
|
||||
books.ForEach(b => b.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated);
|
||||
LiberateClicked?.Invoke(this, [entry3_a.LibraryBook], config);
|
||||
})
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
#region Convert to Mp3
|
||||
|
||||
@@ -329,7 +348,7 @@ namespace LibationAvalonia.Views
|
||||
entry4.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
|
||||
if (entry4.Book.HasPdf)
|
||||
entry4.Book.UserDefinedItem.SetPdfStatus(LiberatedStatus.NotLiberated);
|
||||
LiberateClicked?.Invoke(this, [entry4.LibraryBook]);
|
||||
LiberateClicked?.Invoke(this, [entry4.LibraryBook], Configuration.Instance);
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -512,7 +531,7 @@ namespace LibationAvalonia.Views
|
||||
}
|
||||
else if (button.DataContext is LibraryBookEntry lbEntry)
|
||||
{
|
||||
LiberateClicked?.Invoke(this, [lbEntry.LibraryBook]);
|
||||
LiberateClicked?.Invoke(this, [lbEntry.LibraryBook], Configuration.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ namespace LibationCli
|
||||
public IEnumerable<string>? Asins { get; set; }
|
||||
|
||||
protected static TProcessable CreateProcessable<TProcessable>(EventHandler<LibraryBook>? completedAction = null)
|
||||
where TProcessable : Processable, new()
|
||||
where TProcessable : Processable, IProcessable<TProcessable>
|
||||
{
|
||||
var progressBar = new ConsoleProgressBar(Console.Out);
|
||||
var strProc = new TProcessable();
|
||||
var strProc = TProcessable.Create(Configuration.Instance);
|
||||
LibraryBook? currentLibraryBook = null;
|
||||
|
||||
strProc.Begin += (o, e) =>
|
||||
|
||||
@@ -32,7 +32,14 @@ namespace LibationFileManager
|
||||
}
|
||||
private static readonly Configuration s_SingletonInstance = new();
|
||||
public static Configuration Instance { get; private set; } = s_SingletonInstance;
|
||||
public bool IsEphemeralInstance => JsonBackedDictionary is EphemeralDictionary;
|
||||
|
||||
public Configuration CreateEphemeralCopy()
|
||||
{
|
||||
var copy = new Configuration();
|
||||
copy.LoadEphemeralSettings(Settings.GetJObject());
|
||||
return copy;
|
||||
}
|
||||
|
||||
private Configuration() { }
|
||||
#endregion
|
||||
|
||||
@@ -17,6 +17,7 @@ internal class EphemeralDictionary : IJsonBackedDictionary
|
||||
JsonObject = dataStore;
|
||||
}
|
||||
|
||||
public JObject GetJObject() => (JObject)JsonObject.DeepClone();
|
||||
public bool Exists(string propertyName)
|
||||
=> JsonObject.ContainsKey(propertyName);
|
||||
public string? GetString(string propertyName, string? defaultValue = null)
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace LibationUiBase.GridView;
|
||||
|
||||
public delegate void LiberateClickedHandler(object sender, System.Collections.Generic.IList<LibraryBook> libraryBooks, Configuration config);
|
||||
public class GridContextMenu
|
||||
{
|
||||
public string CopyCellText => $"{Accelerator}Copy Cell Contents";
|
||||
@@ -20,6 +21,7 @@ public class GridContextMenu
|
||||
public string LocateFileDialogTitle => $"Locate the audio file for '{GridEntries[0].Book.TitleWithSubtitle}'";
|
||||
public string LocateFileErrorMessage => "Error saving book's location";
|
||||
public string ConvertToMp3Text => $"{Accelerator}Convert to Mp3";
|
||||
public string DownloadAsChapters => $"Download {Accelerator}split by chapters";
|
||||
public string ReDownloadText => "Re-download this audiobook";
|
||||
public string DownloadSelectedText => "Download selected audiobooks";
|
||||
public string EditTemplatesText => "Edit Templates";
|
||||
@@ -33,6 +35,7 @@ public class GridContextMenu
|
||||
public bool SetDownloadedEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || ge.Liberate.IsSeries);
|
||||
public bool SetNotDownloadedEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || ge.Liberate.IsSeries);
|
||||
public bool ConvertToMp3Enabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated);
|
||||
public bool DownloadAsChaptersEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Error);
|
||||
public bool ReDownloadEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated);
|
||||
|
||||
private GridEntry[] GridEntries { get; }
|
||||
|
||||
@@ -43,6 +43,7 @@ public enum ProcessBookStatus
|
||||
public class ProcessBookViewModel : ReactiveObject
|
||||
{
|
||||
public LibraryBook LibraryBook { get; protected set; }
|
||||
public Configuration Configuration { get; }
|
||||
|
||||
#region Properties exposed to the view
|
||||
public ProcessBookResult Result { get => field; set { RaiseAndSetIfChanged(ref field, value); RaisePropertyChanged(nameof(StatusText)); } }
|
||||
@@ -95,9 +96,10 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
/// <summary> A series of Processable actions to perform on this book </summary>
|
||||
protected Queue<Func<Processable>> Processes { get; } = new();
|
||||
|
||||
public ProcessBookViewModel(LibraryBook libraryBook)
|
||||
public ProcessBookViewModel(LibraryBook libraryBook, Configuration configuration)
|
||||
{
|
||||
LibraryBook = libraryBook;
|
||||
Configuration = configuration;
|
||||
|
||||
Title = LibraryBook.Book.TitleWithSubtitle;
|
||||
Author = LibraryBook.Book.AuthorNames;
|
||||
@@ -203,9 +205,9 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
public ProcessBookViewModel AddDownloadDecryptBook() => AddProcessable<DownloadDecryptBook>();
|
||||
public ProcessBookViewModel AddConvertToMp3() => AddProcessable<ConvertToMp3>();
|
||||
|
||||
private ProcessBookViewModel AddProcessable<T>() where T : Processable, new()
|
||||
private ProcessBookViewModel AddProcessable<T>() where T : Processable, IProcessable<T>
|
||||
{
|
||||
Processes.Enqueue(() => new T());
|
||||
Processes.Enqueue(() => T.Create(Configuration));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -260,7 +262,7 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
private byte[] AudioDecodable_RequestCoverArt(object? sender, EventArgs e)
|
||||
{
|
||||
var quality
|
||||
= Configuration.Instance.FileDownloadQuality == Configuration.DownloadQuality.High && LibraryBook.Book.PictureLarge is not null
|
||||
= Configuration.FileDownloadQuality == Configuration.DownloadQuality.High && LibraryBook.Book.PictureLarge is not null
|
||||
? new PictureDefinition(LibraryBook.Book.PictureLarge, PictureSize.Native)
|
||||
: new PictureDefinition(LibraryBook.Book.PictureId, PictureSize._500x500);
|
||||
|
||||
@@ -345,7 +347,7 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
const DialogResult SkipResult = DialogResult.Ignore;
|
||||
LogError($"ERROR. All books have not been processed. Book failed: {libraryBook.Book}");
|
||||
|
||||
DialogResult? dialogResult = Configuration.Instance.BadBook switch
|
||||
DialogResult? dialogResult = Configuration.BadBook switch
|
||||
{
|
||||
Configuration.BadBookAction.Abort => DialogResult.Abort,
|
||||
Configuration.BadBookAction.Retry => DialogResult.Retry,
|
||||
|
||||
@@ -27,7 +27,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
{
|
||||
Queue.QueuedCountChanged += Queue_QueuedCountChanged;
|
||||
Queue.CompletedCountChanged += Queue_CompletedCountChanged;
|
||||
SpeedLimit = LibationFileManager.Configuration.Instance.DownloadSpeedLimit / 1024m / 1024;
|
||||
SpeedLimit = Configuration.Instance.DownloadSpeedLimit / 1024m / 1024;
|
||||
}
|
||||
|
||||
public int CompletedCount { get => field; private set { RaiseAndSetIfChanged(ref field, value); RaisePropertyChanged(nameof(AnyCompleted)); } }
|
||||
@@ -48,7 +48,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
set
|
||||
{
|
||||
var newValue = Math.Min(999 * 1024 * 1024, (long)Math.Ceiling(value * 1024 * 1024));
|
||||
var config = LibationFileManager.Configuration.Instance;
|
||||
var config = Configuration.Instance;
|
||||
config.DownloadSpeedLimit = newValue;
|
||||
|
||||
_speedLimit
|
||||
@@ -57,6 +57,8 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
: 0;
|
||||
|
||||
config.DownloadSpeedLimit = (long)(_speedLimit * 1024 * 1024);
|
||||
if (Queue.Current is ProcessBookViewModel currentBook)
|
||||
currentBook.Configuration.DownloadSpeedLimit = config.DownloadSpeedLimit;
|
||||
|
||||
SpeedLimitIncrement = _speedLimit > 100 ? 10
|
||||
: _speedLimit > 10 ? 1
|
||||
@@ -89,24 +91,26 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
|
||||
#region Add Books to Queue
|
||||
|
||||
public bool QueueDownloadPdf(IList<LibraryBook> libraryBooks)
|
||||
public bool QueueDownloadPdf(IList<LibraryBook> libraryBooks, Configuration? config = null)
|
||||
{
|
||||
if (!IsBooksDirectoryValid())
|
||||
config ??= Configuration.Instance;
|
||||
if (!IsBooksDirectoryValid(config))
|
||||
return false;
|
||||
|
||||
var needsPdf = libraryBooks.Where(lb => lb.NeedsPdfDownload()).ToArray();
|
||||
if (needsPdf.Length > 0)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin download {count} pdfs", needsPdf.Length);
|
||||
AddDownloadPdf(needsPdf);
|
||||
AddDownloadPdf(needsPdf, config);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool QueueConvertToMp3(IList<LibraryBook> libraryBooks)
|
||||
public bool QueueConvertToMp3(IList<LibraryBook> libraryBooks, Configuration? config = null)
|
||||
{
|
||||
if (!IsBooksDirectoryValid())
|
||||
config ??= Configuration.Instance;
|
||||
if (!IsBooksDirectoryValid(config))
|
||||
return false;
|
||||
|
||||
//Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing.
|
||||
@@ -116,15 +120,16 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
if (preLiberated.Length == 1)
|
||||
RemoveCompleted(preLiberated[0]);
|
||||
Serilog.Log.Logger.Information("Begin convert {count} books to mp3", preLiberated.Length);
|
||||
AddConvertMp3(preLiberated);
|
||||
AddConvertMp3(preLiberated, config);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool QueueDownloadDecrypt(IList<LibraryBook> libraryBooks)
|
||||
public bool QueueDownloadDecrypt(IList<LibraryBook> libraryBooks, Configuration? config = null)
|
||||
{
|
||||
if (!IsBooksDirectoryValid())
|
||||
config ??= Configuration.Instance;
|
||||
if (!IsBooksDirectoryValid(config))
|
||||
return false;
|
||||
|
||||
if (libraryBooks.Count == 1)
|
||||
@@ -137,14 +142,14 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
{
|
||||
RemoveCompleted(item);
|
||||
Serilog.Log.Logger.Information("Begin single library book backup of {libraryBook}", item);
|
||||
AddDownloadDecrypt([item]);
|
||||
AddDownloadDecrypt([item], config);
|
||||
return true;
|
||||
}
|
||||
else if (item.NeedsPdfDownload())
|
||||
{
|
||||
RemoveCompleted(item);
|
||||
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", item);
|
||||
AddDownloadPdf([item]);
|
||||
AddDownloadPdf([item], config);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -155,16 +160,16 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
if (toLiberate.Length > 0)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin backup of {count} library books", toLiberate.Length);
|
||||
AddDownloadDecrypt(toLiberate);
|
||||
AddDownloadDecrypt(toLiberate, config);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsBooksDirectoryValid()
|
||||
private bool IsBooksDirectoryValid(Configuration config)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Configuration.Instance.Books))
|
||||
if (string.IsNullOrWhiteSpace(config.Books))
|
||||
{
|
||||
Serilog.Log.Logger.Error("Books location is not set in configuration.");
|
||||
MessageBoxBase.Show(
|
||||
@@ -176,9 +181,9 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
}
|
||||
else if (AudibleFileStorage.BooksDirectory is null)
|
||||
{
|
||||
Serilog.Log.Logger.Error("Failed to create books directory: {@booksDir}", Configuration.Instance.Books);
|
||||
Serilog.Log.Logger.Error("Failed to create books directory: {@booksDir}", config.Books);
|
||||
MessageBoxBase.Show(
|
||||
$"Libation was unable to create the \"Books location\" folder at:\n{Configuration.Instance.Books}\n\nPlease change the Books location in the settings menu.",
|
||||
$"Libation was unable to create the \"Books location\" folder at:\n{config.Books}\n\nPlease change the Books location in the settings menu.",
|
||||
"Failed to Create Books Directory",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
@@ -186,9 +191,9 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
}
|
||||
else if (AudibleFileStorage.DownloadsInProgressDirectory is null)
|
||||
{
|
||||
Serilog.Log.Logger.Error("Failed to create DownloadsInProgressDirectory in {@InProgress}", Configuration.Instance.InProgress);
|
||||
Serilog.Log.Logger.Error("Failed to create DownloadsInProgressDirectory in {@InProgress}", config.InProgress);
|
||||
MessageBoxBase.Show(
|
||||
$"Libation was unable to create the \"Downloads In Progress\" folder in:\n{Configuration.Instance.InProgress}\n\nPlease change the In Progress location in the settings menu.",
|
||||
$"Libation was unable to create the \"Downloads In Progress\" folder in:\n{config.InProgress}\n\nPlease change the In Progress location in the settings menu.",
|
||||
"Failed to Create Downloads In Progress Directory",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
@@ -196,9 +201,9 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
}
|
||||
else if (AudibleFileStorage.DecryptInProgressDirectory is null)
|
||||
{
|
||||
Serilog.Log.Logger.Error("Failed to create DecryptInProgressDirectory in {@InProgress}", Configuration.Instance.InProgress);
|
||||
Serilog.Log.Logger.Error("Failed to create DecryptInProgressDirectory in {@InProgress}", config.InProgress);
|
||||
MessageBoxBase.Show(
|
||||
$"Libation was unable to create the \"Decrypt In Progress\" folder in:\n{Configuration.Instance.InProgress}\n\nPlease change the In Progress location in the settings menu.",
|
||||
$"Libation was unable to create the \"Decrypt In Progress\" folder in:\n{config.InProgress}\n\nPlease change the In Progress location in the settings menu.",
|
||||
"Failed to Create Decrypt In Progress Directory",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
@@ -218,34 +223,34 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
&& entry.Status is ProcessBookStatus.Completed
|
||||
&& Queue.RemoveCompleted(entry);
|
||||
|
||||
private void AddDownloadPdf(IList<LibraryBook> entries)
|
||||
private void AddDownloadPdf(IList<LibraryBook> entries, Configuration config)
|
||||
{
|
||||
var procs = entries.Where(e => !IsBookInQueue(e)).Select(Create).ToArray();
|
||||
Serilog.Log.Logger.Information("Queueing {count} books for PDF-only download", procs.Length);
|
||||
AddToQueue(procs);
|
||||
|
||||
ProcessBookViewModel Create(LibraryBook entry)
|
||||
=> new ProcessBookViewModel(entry).AddDownloadPdf();
|
||||
=> new ProcessBookViewModel(entry, config).AddDownloadPdf();
|
||||
}
|
||||
|
||||
private void AddDownloadDecrypt(IList<LibraryBook> entries)
|
||||
private void AddDownloadDecrypt(IList<LibraryBook> entries, Configuration config)
|
||||
{
|
||||
var procs = entries.Where(e => !IsBookInQueue(e)).Select(Create).ToArray();
|
||||
Serilog.Log.Logger.Information("Queueing {count} books ofr download/decrypt", procs.Length);
|
||||
AddToQueue(procs);
|
||||
|
||||
ProcessBookViewModel Create(LibraryBook entry)
|
||||
=> new ProcessBookViewModel(entry).AddDownloadDecryptBook().AddDownloadPdf();
|
||||
=> new ProcessBookViewModel(entry, config).AddDownloadDecryptBook().AddDownloadPdf();
|
||||
}
|
||||
|
||||
private void AddConvertMp3(IList<LibraryBook> entries)
|
||||
private void AddConvertMp3(IList<LibraryBook> entries, Configuration config)
|
||||
{
|
||||
var procs = entries.Where(e => !IsBookInQueue(e)).Select(Create).ToArray();
|
||||
Serilog.Log.Logger.Information("Queueing {count} books for mp3 conversion", procs.Length);
|
||||
AddToQueue(procs);
|
||||
|
||||
ProcessBookViewModel Create(LibraryBook entry)
|
||||
=> new ProcessBookViewModel(entry).AddConvertToMp3();
|
||||
=> new ProcessBookViewModel(entry, config).AddConvertToMp3();
|
||||
}
|
||||
|
||||
private void AddToQueue(IList<ProcessBookViewModel> pbook)
|
||||
@@ -282,7 +287,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
}
|
||||
|
||||
Serilog.Log.Logger.Information("Begin processing queued item: '{item_LibraryBook}'", nextBook.LibraryBook);
|
||||
|
||||
SpeedLimit = nextBook.Configuration.DownloadSpeedLimit / 1024m / 1024;
|
||||
var result = await nextBook.ProcessOneAsync();
|
||||
|
||||
Serilog.Log.Logger.Information("Completed processing queued item: '{item_LibraryBook}' with result: {result}", nextBook.LibraryBook, result);
|
||||
|
||||
@@ -25,13 +25,13 @@ namespace LibationWinForms
|
||||
this.Width = width;
|
||||
}
|
||||
|
||||
private void ProductsDisplay_LiberateClicked(object sender, LibraryBook[] libraryBooks)
|
||||
private void ProductsDisplay_LiberateClicked(object sender, System.Collections.Generic.IList<LibraryBook> libraryBooks, Configuration config)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (processBookQueue1.ViewModel.QueueDownloadDecrypt(libraryBooks))
|
||||
if (processBookQueue1.ViewModel.QueueDownloadDecrypt(libraryBooks, config))
|
||||
SetQueueCollapseState(false);
|
||||
else if (libraryBooks.Length == 1 && libraryBooks[0].Book.AudioExists)
|
||||
else if (libraryBooks.Count == 1 && libraryBooks[0].Book.AudioExists)
|
||||
{
|
||||
// liberated: open explorer to file
|
||||
var filePath = AudibleFileStorage.Audio.GetPath(libraryBooks[0].Book.AudibleProductId);
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace LibationWinForms.GridView
|
||||
/// <summary>Number of visible rows has changed</summary>
|
||||
public event EventHandler<int> VisibleCountChanged;
|
||||
public event EventHandler<int> RemovableCountChanged;
|
||||
public event EventHandler<LibraryBook[]> LiberateClicked;
|
||||
public event LiberateClickedHandler LiberateClicked;
|
||||
public event EventHandler<SeriesEntry> LiberateSeriesClicked;
|
||||
public event EventHandler<LibraryBook[]> ConvertToMp3Clicked;
|
||||
public event EventHandler InitialLoaded;
|
||||
@@ -202,10 +202,31 @@ namespace LibationWinForms.GridView
|
||||
ctxMenu.Items.Add(downloadSelectedMenuItem);
|
||||
downloadSelectedMenuItem.Click += (s, _) =>
|
||||
{
|
||||
LiberateClicked?.Invoke(s, ctx.LibraryBookEntries.Select(e => e.LibraryBook).ToArray());
|
||||
LiberateClicked?.Invoke(s, ctx.LibraryBookEntries.Select(e => e.LibraryBook).ToArray(), Configuration.Instance);
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region Download split by chapters
|
||||
|
||||
if (ctx.LibraryBookEntries.Length > 0)
|
||||
{
|
||||
var downloadChaptersMenuItem = new ToolStripMenuItem
|
||||
{
|
||||
Text = ctx.DownloadAsChapters,
|
||||
Enabled = ctx.DownloadAsChaptersEnabled
|
||||
};
|
||||
downloadChaptersMenuItem.Click += (_, e) =>
|
||||
{
|
||||
var config = Configuration.Instance.CreateEphemeralCopy();
|
||||
config.AllowLibationFixup = config.SplitFilesByChapter = true;
|
||||
var books = ctx.LibraryBookEntries.Select(e => e.LibraryBook).Where(lb => lb.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Error).ToList();
|
||||
books.ForEach(b => b.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated);
|
||||
LiberateClicked?.Invoke(this, books, config);
|
||||
};
|
||||
ctxMenu.Items.Add(downloadChaptersMenuItem);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region Convert to Mp3
|
||||
|
||||
@@ -218,7 +239,6 @@ namespace LibationWinForms.GridView
|
||||
};
|
||||
convertToMp3MenuItem.Click += (_, e) => ConvertToMp3Clicked?.Invoke(this, ctx.LibraryBookEntries.Select(e => e.LibraryBook).ToArray());
|
||||
ctxMenu.Items.Add(convertToMp3MenuItem);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -239,7 +259,7 @@ namespace LibationWinForms.GridView
|
||||
if (entry4.Book.HasPdf)
|
||||
entry4.Book.UserDefinedItem.SetPdfStatus(LiberatedStatus.NotLiberated);
|
||||
|
||||
LiberateClicked?.Invoke(s, [entry4.LibraryBook]);
|
||||
LiberateClicked?.Invoke(s, [entry4.LibraryBook], Configuration.Instance);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -415,7 +435,7 @@ namespace LibationWinForms.GridView
|
||||
{
|
||||
if (liveGridEntry.LibraryBook.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Error
|
||||
&& !liveGridEntry.Liberate.IsUnavailable)
|
||||
LiberateClicked?.Invoke(this, [liveGridEntry.LibraryBook]);
|
||||
LiberateClicked?.Invoke(this, [liveGridEntry.LibraryBook], Configuration.Instance);
|
||||
}
|
||||
|
||||
private void productsGrid_RemovableCountChanged(object sender, EventArgs e)
|
||||
|
||||
Reference in New Issue
Block a user