Add Configurations property change notifications

This commit is contained in:
Michael Bucari-Tovo 2023-01-06 16:24:29 -07:00
parent f09baa1318
commit 1f7000c2c9
26 changed files with 531 additions and 259 deletions

View File

@ -43,7 +43,21 @@ namespace AaxDecrypter
return false; return false;
} }
//Step 3 //Step 3
if (DownloadOptions.DownloadClipsBookmarks)
{
Serilog.Log.Information("Begin Downloading Clips and Bookmarks");
if (await Task.Run(Step_DownloadClipsBookmarks))
Serilog.Log.Information("Completed Downloading Clips and Bookmarks");
else
{
Serilog.Log.Information("Failed to Download Clips and Bookmarks");
return false;
}
}
//Step 4
Serilog.Log.Information("Begin Cleanup"); Serilog.Log.Information("Begin Cleanup");
if (await Task.Run(Step_Cleanup)) if (await Task.Run(Step_Cleanup))
Serilog.Log.Information("Completed Cleanup"); Serilog.Log.Information("Completed Cleanup");

View File

@ -49,6 +49,19 @@ namespace AaxDecrypter
} }
//Step 4 //Step 4
if (DownloadOptions.DownloadClipsBookmarks)
{
Serilog.Log.Information("Begin Downloading Clips and Bookmarks");
if (await Task.Run(Step_DownloadClipsBookmarks))
Serilog.Log.Information("Completed Downloading Clips and Bookmarks");
else
{
Serilog.Log.Information("Failed to Download Clips and Bookmarks");
return false;
}
}
//Step 5
Serilog.Log.Information("Begin Step 4: Cleanup"); Serilog.Log.Information("Begin Step 4: Cleanup");
if (await Task.Run(Step_Cleanup)) if (await Task.Run(Step_Cleanup))
Serilog.Log.Information("Completed Step 4: Cleanup"); Serilog.Log.Information("Completed Step 4: Cleanup");

View File

@ -48,11 +48,18 @@ namespace AaxDecrypter
TempFilePath = Path.ChangeExtension(jsonDownloadState, ".aaxc"); TempFilePath = Path.ChangeExtension(jsonDownloadState, ".aaxc");
DownloadOptions = ArgumentValidator.EnsureNotNull(dlOptions, nameof(dlOptions)); DownloadOptions = ArgumentValidator.EnsureNotNull(dlOptions, nameof(dlOptions));
DownloadOptions.PropertyChanged += DownloadOptions_PropertyChanged;
// delete file after validation is complete // delete file after validation is complete
FileUtility.SaferDelete(OutputFileName); FileUtility.SaferDelete(OutputFileName);
} }
private void DownloadOptions_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(DownloadOptions.DownloadSpeedBps))
InputFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
}
public abstract Task CancelAsync(); public abstract Task CancelAsync();
public virtual void SetCoverArt(byte[] coverArt) public virtual void SetCoverArt(byte[] coverArt)
@ -132,14 +139,27 @@ namespace AaxDecrypter
return success; return success;
} }
protected async Task<bool> Step_DownloadClipsBookmarks()
{
if (!IsCanceled && DownloadOptions.DownloadClipsBookmarks)
{
var recordsFile = await DownloadOptions.SaveClipsAndBookmarks(OutputFileName);
if (File.Exists(recordsFile))
OnFileCreated(recordsFile);
}
return !IsCanceled;
}
private NetworkFileStreamPersister OpenNetworkFileStream() private NetworkFileStreamPersister OpenNetworkFileStream()
{ {
if (!File.Exists(jsonDownloadState)) NetworkFileStreamPersister nfsp = default;
return NewNetworkFilePersister();
try try
{ {
var nfsp = new NetworkFileStreamPersister(jsonDownloadState); if (!File.Exists(jsonDownloadState))
return nfsp = NewNetworkFilePersister();
nfsp = new NetworkFileStreamPersister(jsonDownloadState);
// If More than ~1 hour has elapsed since getting the download url, it will expire. // If More than ~1 hour has elapsed since getting the download url, it will expire.
// The new url will be to the same file. // The new url will be to the same file.
nfsp.NetworkFileStream.SetUriForSameFile(new Uri(DownloadOptions.DownloadUrl)); nfsp.NetworkFileStream.SetUriForSameFile(new Uri(DownloadOptions.DownloadUrl));
@ -149,7 +169,12 @@ namespace AaxDecrypter
{ {
FileUtility.SaferDelete(jsonDownloadState); FileUtility.SaferDelete(jsonDownloadState);
FileUtility.SaferDelete(TempFilePath); FileUtility.SaferDelete(TempFilePath);
return NewNetworkFilePersister(); return nfsp = NewNetworkFilePersister();
}
finally
{
if (nfsp is not null)
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
} }
} }

View File

@ -1,8 +1,10 @@
using AAXClean; using AAXClean;
using System.ComponentModel;
using System.Threading.Tasks;
namespace AaxDecrypter namespace AaxDecrypter
{ {
public interface IDownloadOptions public interface IDownloadOptions : INotifyPropertyChanged
{ {
FileManager.ReplacementCharacters ReplacementCharacters { get; } FileManager.ReplacementCharacters ReplacementCharacters { get; }
string DownloadUrl { get; } string DownloadUrl { get; }
@ -14,6 +16,8 @@ namespace AaxDecrypter
bool RetainEncryptedFile { get; } bool RetainEncryptedFile { get; }
bool StripUnabridged { get; } bool StripUnabridged { get; }
bool CreateCueSheet { get; } bool CreateCueSheet { get; }
bool DownloadClipsBookmarks { get; }
long DownloadSpeedBps { get; }
ChapterInfo ChapterInfo { get; } ChapterInfo ChapterInfo { get; }
bool FixupFile { get; } bool FixupFile { get; }
NAudio.Lame.LameConfig LameConfig { get; } NAudio.Lame.LameConfig LameConfig { get; }
@ -21,5 +25,6 @@ namespace AaxDecrypter
bool MatchSourceBitrate { get; } bool MatchSourceBitrate { get; }
string GetMultipartFileName(MultiConvertFileProperties props); string GetMultipartFileName(MultiConvertFileProperties props);
string GetMultipartTitleName(MultiConvertFileProperties props); string GetMultipartTitleName(MultiConvertFileProperties props);
Task<string> SaveClipsAndBookmarks(string fileName);
} }
} }

View File

@ -41,9 +41,9 @@ namespace AaxDecrypter
[JsonIgnore] [JsonIgnore]
public bool IsCancelled => _cancellationSource.IsCancellationRequested; public bool IsCancelled => _cancellationSource.IsCancellationRequested;
private static long _globalSpeedLimit = 0; private long _speedLimit = 0;
/// <summary>bytes per second</summary> /// <summary>bytes per second</summary>
public static long GlobalSpeedLimit { get => _globalSpeedLimit; set => _globalSpeedLimit = value <= 0 ? 0 : Math.Max(value, MIN_BYTES_PER_SECOND); } public long SpeedLimit { get => _speedLimit; set => _speedLimit = value <= 0 ? 0 : Math.Max(value, MIN_BYTES_PER_SECOND); }
#endregion #endregion
@ -70,7 +70,7 @@ namespace AaxDecrypter
//Minimum throttle rate. The minimum amount of data that can be throttled //Minimum throttle rate. The minimum amount of data that can be throttled
//on each iteration of the download loop is DOWNLOAD_BUFF_SZ. //on each iteration of the download loop is DOWNLOAD_BUFF_SZ.
private const int MIN_BYTES_PER_SECOND = DOWNLOAD_BUFF_SZ * THROTTLE_FREQUENCY; public const int MIN_BYTES_PER_SECOND = DOWNLOAD_BUFF_SZ * THROTTLE_FREQUENCY;
#endregion #endregion
@ -202,7 +202,7 @@ namespace AaxDecrypter
bytesReadSinceThrottle += bytesRead; bytesReadSinceThrottle += bytesRead;
if (GlobalSpeedLimit >= MIN_BYTES_PER_SECOND && bytesReadSinceThrottle > GlobalSpeedLimit / THROTTLE_FREQUENCY) if (SpeedLimit >= MIN_BYTES_PER_SECOND && bytesReadSinceThrottle > SpeedLimit / THROTTLE_FREQUENCY)
{ {
var delayMS = (int)(startTime.AddSeconds(1d / THROTTLE_FREQUENCY) - DateTime.Now).TotalMilliseconds; var delayMS = (int)(startTime.AddSeconds(1d / THROTTLE_FREQUENCY) - DateTime.Now).TotalMilliseconds;
if (delayMS > 0) if (delayMS > 0)

View File

@ -16,7 +16,7 @@ namespace AaxDecrypter
{ {
try try
{ {
Serilog.Log.Information("Begin download and convert Aaxc To {format}", DownloadOptions.OutputFormat); Serilog.Log.Information("Begin downloading unencrypted audiobook.");
//Step 1 //Step 1
Serilog.Log.Information("Begin Step 1: Get Mp3 Metadata"); Serilog.Log.Information("Begin Step 1: Get Mp3 Metadata");
@ -39,6 +39,19 @@ namespace AaxDecrypter
} }
//Step 3 //Step 3
if (DownloadOptions.DownloadClipsBookmarks)
{
Serilog.Log.Information("Begin Downloading Clips and Bookmarks");
if (await Task.Run(Step_DownloadClipsBookmarks))
Serilog.Log.Information("Completed Downloading Clips and Bookmarks");
else
{
Serilog.Log.Information("Failed to Download Clips and Bookmarks");
return false;
}
}
//Step 4
Serilog.Log.Information("Begin Step 3: Cleanup"); Serilog.Log.Information("Begin Step 3: Cleanup");
if (await Task.Run(Step_Cleanup)) if (await Task.Run(Step_Cleanup))
Serilog.Log.Information("Completed Step 3: Cleanup"); Serilog.Log.Information("Completed Step 3: Cleanup");
@ -58,7 +71,6 @@ namespace AaxDecrypter
} }
} }
public override Task CancelAsync() public override Task CancelAsync()
{ {
IsCanceled = true; IsCanceled = true;

View File

@ -174,6 +174,12 @@ namespace AppScaffolding
if (!config.Exists(nameof(config.DownloadCoverArt))) if (!config.Exists(nameof(config.DownloadCoverArt)))
config.DownloadCoverArt = true; config.DownloadCoverArt = true;
if (!config.Exists(nameof(config.DownloadClipsBookmarks)))
config.DownloadClipsBookmarks = false;
if (!config.Exists(nameof(config.ClipsBookmarksFileFormat)))
config.ClipsBookmarksFileFormat = Configuration.ClipBookmarkFormat.CSV;
if (!config.Exists(nameof(config.AutoDownloadEpisodes))) if (!config.Exists(nameof(config.AutoDownloadEpisodes)))
config.AutoDownloadEpisodes = false; config.AutoDownloadEpisodes = false;
@ -229,7 +235,7 @@ namespace AppScaffolding
{ "Using", new JArray{ "Dinah.Core", "Serilog.Exceptions" } }, // dll's name, NOT namespace { "Using", new JArray{ "Dinah.Core", "Serilog.Exceptions" } }, // dll's name, NOT namespace
{ "Enrich", new JArray{ "WithCaller", "WithExceptionDetails" } }, { "Enrich", new JArray{ "WithCaller", "WithExceptionDetails" } },
}; };
config.SetObject("Serilog", serilogObj); config.SetNonString(serilogObj, "Serilog");
} }
// to restore original: Console.SetOut(origOut); // to restore original: Console.SetOut(origOut);

View File

@ -104,7 +104,7 @@ namespace FileLiberator
var api = await libraryBook.GetApiAsync(); var api = await libraryBook.GetApiAsync();
var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId); var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId);
var dlOptions = BuildDownloadOptions(libraryBook, config, contentLic); using var dlOptions = BuildDownloadOptions(libraryBook, config, contentLic);
var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, dlOptions.OutputFormat.ToString().ToLower()); var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, dlOptions.OutputFormat.ToString().ToLower());
var cacheDir = AudibleFileStorage.DownloadsInProgressDirectory; var cacheDir = AudibleFileStorage.DownloadsInProgressDirectory;
@ -133,9 +133,7 @@ namespace FileLiberator
abDownloader.FileCreated += (_, path) => OnFileCreated(libraryBook, path); abDownloader.FileCreated += (_, path) => OnFileCreated(libraryBook, path);
// REAL WORK DONE HERE // REAL WORK DONE HERE
var success = await abDownloader.RunAsync(); return await abDownloader.RunAsync();
return success;
} }
private DownloadOptions BuildDownloadOptions(LibraryBook libraryBook, Configuration config, AudibleApi.Common.ContentLicense contentLic) private DownloadOptions BuildDownloadOptions(LibraryBook libraryBook, Configuration config, AudibleApi.Common.ContentLicense contentLic)
@ -168,6 +166,8 @@ namespace FileLiberator
Downsample = config.AllowLibationFixup && config.LameDownsampleMono, Downsample = config.AllowLibationFixup && config.LameDownsampleMono,
MatchSourceBitrate = config.AllowLibationFixup && config.LameMatchSourceBR && config.LameTargetBitrate, MatchSourceBitrate = config.AllowLibationFixup && config.LameMatchSourceBR && config.LameTargetBitrate,
CreateCueSheet = config.CreateCueSheet, CreateCueSheet = config.CreateCueSheet,
DownloadClipsBookmarks = config.DownloadClipsBookmarks,
DownloadSpeedBps = config.DownloadSpeedLimit,
LameConfig = GetLameOptions(config), LameConfig = GetLameOptions(config),
ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(chapterStartMs)), ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(chapterStartMs)),
FixupFile = config.AllowLibationFixup FixupFile = config.AllowLibationFixup

View File

@ -4,11 +4,20 @@ using Dinah.Core;
using DataLayer; using DataLayer;
using LibationFileManager; using LibationFileManager;
using FileManager; using FileManager;
using System.Threading.Tasks;
using System.ComponentModel;
using System;
using System.IO;
using ApplicationServices;
namespace FileLiberator namespace FileLiberator
{ {
public class DownloadOptions : IDownloadOptions public class DownloadOptions : IDownloadOptions, IDisposable
{ {
public event PropertyChangedEventHandler PropertyChanged;
private readonly IDisposable cancellation;
public LibraryBook LibraryBook { get; }
public LibraryBookDto LibraryBookDto { get; } public LibraryBookDto LibraryBookDto { get; }
public string DownloadUrl { get; } public string DownloadUrl { get; }
public string UserAgent { get; } public string UserAgent { get; }
@ -19,6 +28,8 @@ namespace FileLiberator
public bool RetainEncryptedFile { get; init; } public bool RetainEncryptedFile { get; init; }
public bool StripUnabridged { get; init; } public bool StripUnabridged { get; init; }
public bool CreateCueSheet { get; init; } public bool CreateCueSheet { get; init; }
public bool DownloadClipsBookmarks { get; init; }
public long DownloadSpeedBps { get; set; }
public ChapterInfo ChapterInfo { get; init; } public ChapterInfo ChapterInfo { get; init; }
public bool FixupFile { get; init; } public bool FixupFile { get; init; }
public NAudio.Lame.LameConfig LameConfig { get; init; } public NAudio.Lame.LameConfig LameConfig { get; init; }
@ -32,14 +43,55 @@ namespace FileLiberator
public string GetMultipartTitleName(MultiConvertFileProperties props) public string GetMultipartTitleName(MultiConvertFileProperties props)
=> Templates.ChapterTitle.GetTitle(LibraryBookDto, props); => Templates.ChapterTitle.GetTitle(LibraryBookDto, props);
public async Task<string> SaveClipsAndBookmarks(string fileName)
{
if (DownloadClipsBookmarks)
{
var format = Configuration.Instance.ClipsBookmarksFileFormat;
var formatExtension = format.ToString().ToLowerInvariant();
var filePath = Path.ChangeExtension(fileName, formatExtension);
var api = await LibraryBook.GetApiAsync();
var records = await api.GetRecordsAsync(LibraryBook.Book.AudibleProductId);
switch(format)
{
case Configuration.ClipBookmarkFormat.CSV:
RecordExporter.ToCsv(filePath, records);
break;
case Configuration.ClipBookmarkFormat.Xlsx:
RecordExporter.ToXlsx(filePath, records);
break;
case Configuration.ClipBookmarkFormat.Json:
RecordExporter.ToJson(filePath, LibraryBook, records);
break;
}
return filePath;
}
return string.Empty;
}
private void DownloadSpeedChanged(string propertyName, long speed)
{
DownloadSpeedBps = speed;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DownloadSpeedBps)));
}
public void Dispose()
{
cancellation?.Dispose();
}
public DownloadOptions(LibraryBook libraryBook, string downloadUrl, string userAgent) public DownloadOptions(LibraryBook libraryBook, string downloadUrl, string userAgent)
{ {
LibraryBookDto = ArgumentValidator LibraryBook = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook));
.EnsureNotNull(libraryBook, nameof(libraryBook))
.ToDto();
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl)); DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
UserAgent = ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent)); UserAgent = ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent));
LibraryBookDto = LibraryBook.ToDto();
cancellation = Configuration.Instance.SubscribeToPropertyChanged<long>(nameof(Configuration.DownloadSpeedLimit), DownloadSpeedChanged);
// no null/empty check for key/iv. unencrypted files do not have them // no null/empty check for key/iv. unencrypted files do not have them
} }
} }

View File

@ -50,7 +50,18 @@ namespace FileManager
{ {
var obj = GetObject(propertyName); var obj = GetObject(propertyName);
if (obj is null) return default; if (obj is null) return default;
if (obj is JValue jValue) return jValue.Value<T>();
if (obj is JValue jValue)
{
if (jValue.Type == JTokenType.String && typeof(T).IsAssignableTo(typeof(Enum)))
{
return
Enum.TryParse(typeof(T), jValue.Value<string>(), out var enumVal)
? (T)enumVal
: Enum.GetValues(typeof(T)).Cast<T>().First();
}
return jValue.Value<T>();
}
if (obj is JObject jObject) return jObject.ToObject<T>(); if (obj is JObject jObject) return jObject.ToObject<T>();
return (T)obj; return (T)obj;
} }

View File

@ -93,7 +93,7 @@ namespace LibationAvalonia
saveState.Width = (int)form.Bounds.Size.Width; saveState.Width = (int)form.Bounds.Size.Width;
saveState.Height = (int)form.Bounds.Size.Height; saveState.Height = (int)form.Bounds.Size.Height;
config.SetObject(form.GetType().Name, saveState); config.SetNonString(saveState, form.GetType().Name);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -56,7 +56,7 @@ namespace LibationAvalonia.Views
public void ToggleQueueHideBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) public void ToggleQueueHideBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{ {
SetQueueCollapseState(_viewModel.QueueOpen); SetQueueCollapseState(_viewModel.QueueOpen);
Configuration.Instance.SetObject(nameof(_viewModel.QueueOpen), _viewModel.QueueOpen); Configuration.Instance.SetNonString(_viewModel.QueueOpen, nameof(_viewModel.QueueOpen));
} }
} }
} }

View File

@ -5,6 +5,7 @@ using Dinah.Core;
using LibationFileManager; using LibationFileManager;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
namespace LibationAvalonia.Views namespace LibationAvalonia.Views
@ -13,6 +14,7 @@ namespace LibationAvalonia.Views
public partial class MainWindow public partial class MainWindow
{ {
private InterruptableTimer autoScanTimer; private InterruptableTimer autoScanTimer;
private IDisposable cancellation;
private void Configure_ScanAuto() private void Configure_ScanAuto()
{ {
@ -53,7 +55,11 @@ namespace LibationAvalonia.Views
AccountsSettingsPersister.Saved += accountsPostSave; AccountsSettingsPersister.Saved += accountsPostSave;
// when autoscan setting is changed, update menu checkbox and run autoscan // when autoscan setting is changed, update menu checkbox and run autoscan
Configuration.Instance.AutoScanChanged += startAutoScan; Configuration.Instance.PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(Configuration.Instance.AutoScan))
startAutoScan();
};
} }
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts; private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;

View File

@ -97,13 +97,13 @@ namespace LibationAvalonia.Views
const string ignoreUpdate = "IgnoreUpdate"; const string ignoreUpdate = "IgnoreUpdate";
var config = Configuration.Instance; var config = Configuration.Instance;
if (config.GetObject(ignoreUpdate)?.ToString() == upgradeProperties.LatestRelease.ToString()) if (config.GetString(ignoreUpdate) == upgradeProperties.LatestRelease.ToString())
return; return;
var notificationResult = await new UpgradeNotification(upgradeProperties).ShowDialog<DialogResult>(this); var notificationResult = await new UpgradeNotification(upgradeProperties).ShowDialog<DialogResult>(this);
if (notificationResult == DialogResult.Ignore) if (notificationResult == DialogResult.Ignore)
config.SetObject(ignoreUpdate, upgradeProperties.LatestRelease.ToString()); config.SetString(upgradeProperties.LatestRelease.ToString(), ignoreUpdate);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -36,6 +36,7 @@ namespace LibationFileManager
} }
set set
{ {
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(nameof(LogLevel), LogLevel, value));
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString()); var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
if (!valueWasChanged) if (!valueWasChanged)
{ {
@ -45,6 +46,8 @@ namespace LibationFileManager
configuration.Reload(); configuration.Reload();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(nameof(LogLevel), value));
Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new
{ {
LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(), LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),

View File

@ -3,7 +3,10 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using FileManager; using FileManager;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace LibationFileManager namespace LibationFileManager
{ {
@ -15,11 +18,29 @@ namespace LibationFileManager
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation // config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
// exceptions: appsettings.json, LibationFiles dir, Settings.json // exceptions: appsettings.json, LibationFiles dir, Settings.json
private PersistentDictionary persistentDictionary; private PersistentDictionary persistentDictionary { get; set; }
public T GetNonString<T>(string propertyName) => persistentDictionary.GetNonString<T>(propertyName); public T GetNonString<T>([CallerMemberName] string propertyName = "") => persistentDictionary.GetNonString<T>(propertyName);
public object GetObject(string propertyName) => persistentDictionary.GetObject(propertyName); public object GetObject([CallerMemberName] string propertyName = "") => persistentDictionary.GetObject(propertyName);
public void SetObject(string propertyName, object newValue) => persistentDictionary.SetNonString(propertyName, newValue); public string GetString([CallerMemberName] string propertyName = "") => persistentDictionary.GetString(propertyName);
public void SetNonString(object newValue, [CallerMemberName] string propertyName = "")
{
var existing = GetType().GetProperty(propertyName)?.GetValue(this);
if (existing?.Equals(newValue) is true) return;
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
persistentDictionary.SetNonString(propertyName, newValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, newValue));
}
public void SetString(string newValue, [CallerMemberName] string propertyName = "")
{
var existing = GetType().GetProperty(propertyName)?.GetValue(this);
if (existing?.Equals(newValue) is true) return;
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
persistentDictionary.SetString(propertyName, newValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, newValue));
}
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary> /// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false) public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
@ -45,160 +66,90 @@ namespace LibationFileManager
public bool Exists(string propertyName) => persistentDictionary.Exists(propertyName); public bool Exists(string propertyName) => persistentDictionary.Exists(propertyName);
[Description("Set cover art as the folder's icon. (Windows only)")] [Description("Set cover art as the folder's icon. (Windows only)")]
public bool UseCoverAsFolderIcon public bool UseCoverAsFolderIcon { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(UseCoverAsFolderIcon));
set => persistentDictionary.SetNonString(nameof(UseCoverAsFolderIcon), value);
}
[Description("Use the beta version of Libation\r\nNew and experimental features, but probably buggy.\r\n(requires restart to take effect)")] [Description("Use the beta version of Libation\r\nNew and experimental features, but probably buggy.\r\n(requires restart to take effect)")]
public bool BetaOptIn public bool BetaOptIn { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(BetaOptIn));
set => persistentDictionary.SetNonString(nameof(BetaOptIn), value);
}
[Description("Location for book storage. Includes destination of newly liberated books")] [Description("Location for book storage. Includes destination of newly liberated books")]
public string Books public string Books { get => GetString(); set => SetString(value); }
{
get => persistentDictionary.GetString(nameof(Books));
set => persistentDictionary.SetString(nameof(Books), value);
}
// temp/working dir(s) should be outside of dropbox // temp/working dir(s) should be outside of dropbox
[Description("Temporary location of files while they're in process of being downloaded and decrypted.\r\nWhen decryption is complete, the final file will be in Books location\r\nRecommend not using a folder which is backed up real time. Eg: Dropbox, iCloud, Google Drive")] [Description("Temporary location of files while they're in process of being downloaded and decrypted.\r\nWhen decryption is complete, the final file will be in Books location\r\nRecommend not using a folder which is backed up real time. Eg: Dropbox, iCloud, Google Drive")]
public string InProgress public string InProgress { get => GetString(); set => SetString(value); }
{
get => persistentDictionary.GetString(nameof(InProgress));
set => persistentDictionary.SetString(nameof(InProgress), value);
}
[Description("Allow Libation to fix up audiobook metadata")] [Description("Allow Libation to fix up audiobook metadata")]
public bool AllowLibationFixup public bool AllowLibationFixup { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(AllowLibationFixup));
set => persistentDictionary.SetNonString(nameof(AllowLibationFixup), value);
}
[Description("Create a cue sheet (.cue)")] [Description("Create a cue sheet (.cue)")]
public bool CreateCueSheet public bool CreateCueSheet { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(CreateCueSheet));
set => persistentDictionary.SetNonString(nameof(CreateCueSheet), value);
}
[Description("Retain the Aax file after successfully decrypting")] [Description("Retain the Aax file after successfully decrypting")]
public bool RetainAaxFile public bool RetainAaxFile { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(RetainAaxFile));
set => persistentDictionary.SetNonString(nameof(RetainAaxFile), value);
}
[Description("Split my books into multiple files by chapter")] [Description("Split my books into multiple files by chapter")]
public bool SplitFilesByChapter public bool SplitFilesByChapter { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(SplitFilesByChapter));
set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value);
}
[Description("Merge Opening/End Credits into the following/preceding chapters")] [Description("Merge Opening/End Credits into the following/preceding chapters")]
public bool MergeOpeningAndEndCredits public bool MergeOpeningAndEndCredits { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(MergeOpeningAndEndCredits));
set => persistentDictionary.SetNonString(nameof(MergeOpeningAndEndCredits), value);
}
[Description("Strip \"(Unabridged)\" from audiobook metadata tags")] [Description("Strip \"(Unabridged)\" from audiobook metadata tags")]
public bool StripUnabridged public bool StripUnabridged { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(StripUnabridged));
set => persistentDictionary.SetNonString(nameof(StripUnabridged), value);
}
[Description("Strip audible branding from the start and end of audiobooks.\r\n(e.g. \"This is Audible\")")] [Description("Strip audible branding from the start and end of audiobooks.\r\n(e.g. \"This is Audible\")")]
public bool StripAudibleBrandAudio public bool StripAudibleBrandAudio { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(StripAudibleBrandAudio));
set => persistentDictionary.SetNonString(nameof(StripAudibleBrandAudio), value);
}
[Description("Decrypt to lossy format?")] [Description("Decrypt to lossy format?")]
public bool DecryptToLossy public bool DecryptToLossy { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(DecryptToLossy));
set => persistentDictionary.SetNonString(nameof(DecryptToLossy), value);
}
[Description("Lame encoder target. true = Bitrate, false = Quality")] [Description("Lame encoder target. true = Bitrate, false = Quality")]
public bool LameTargetBitrate public bool LameTargetBitrate { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(LameTargetBitrate));
set => persistentDictionary.SetNonString(nameof(LameTargetBitrate), value);
}
[Description("Lame encoder downsamples to mono")] [Description("Lame encoder downsamples to mono")]
public bool LameDownsampleMono public bool LameDownsampleMono { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(LameDownsampleMono));
set => persistentDictionary.SetNonString(nameof(LameDownsampleMono), value);
}
[Description("Lame target bitrate [16,320]")] [Description("Lame target bitrate [16,320]")]
public int LameBitrate public int LameBitrate { get => GetNonString<int>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<int>(nameof(LameBitrate));
set => persistentDictionary.SetNonString(nameof(LameBitrate), value);
}
[Description("Restrict encoder to constant bitrate?")] [Description("Restrict encoder to constant bitrate?")]
public bool LameConstantBitrate public bool LameConstantBitrate { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(LameConstantBitrate));
set => persistentDictionary.SetNonString(nameof(LameConstantBitrate), value);
}
[Description("Match the source bitrate?")] [Description("Match the source bitrate?")]
public bool LameMatchSourceBR public bool LameMatchSourceBR { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(LameMatchSourceBR));
set => persistentDictionary.SetNonString(nameof(LameMatchSourceBR), value);
}
[Description("Lame target VBR quality [10,100]")] [Description("Lame target VBR quality [10,100]")]
public int LameVBRQuality public int LameVBRQuality { get => GetNonString<int>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<int>(nameof(LameVBRQuality));
set => persistentDictionary.SetNonString(nameof(LameVBRQuality), value);
}
[Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")] [Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
public Dictionary<string, bool> GridColumnsVisibilities public Dictionary<string, bool> GridColumnsVisibilities { get => GetNonString<Dictionary<string, bool>>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<Dictionary<string, bool>>(nameof(GridColumnsVisibilities));
set => persistentDictionary.SetNonString(nameof(GridColumnsVisibilities), value);
}
[Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")] [Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")]
public Dictionary<string, int> GridColumnsDisplayIndices public Dictionary<string, int> GridColumnsDisplayIndices { get => GetNonString<Dictionary<string, int>>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<Dictionary<string, int>>(nameof(GridColumnsDisplayIndices));
set => persistentDictionary.SetNonString(nameof(GridColumnsDisplayIndices), value);
}
[Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")] [Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")]
public Dictionary<string, int> GridColumnsWidths public Dictionary<string, int> GridColumnsWidths { get => GetNonString<Dictionary<string, int>>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<Dictionary<string, int>>(nameof(GridColumnsWidths));
set => persistentDictionary.SetNonString(nameof(GridColumnsWidths), value);
}
[Description("Save cover image alongside audiobook?")] [Description("Save cover image alongside audiobook?")]
public bool DownloadCoverArt public bool DownloadCoverArt { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Download clips and bookmarks?")]
public bool DownloadClipsBookmarks { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("File format to save clips and bookmarks")]
public ClipBookmarkFormat ClipsBookmarksFileFormat { get => GetNonString<ClipBookmarkFormat>(); set => SetNonString(value); }
[JsonConverter(typeof(StringEnumConverter))]
public enum ClipBookmarkFormat
{ {
get => persistentDictionary.GetNonString<bool>(nameof(DownloadCoverArt)); [Description("Comma-separated values")]
set => persistentDictionary.SetNonString(nameof(DownloadCoverArt), value); CSV,
[Description("Microsoft Excel Spreadsheet")]
Xlsx,
[Description("JavaScript Object Notation (JSON)")]
Json
} }
[JsonConverter(typeof(StringEnumConverter))]
public enum BadBookAction public enum BadBookAction
{ {
[Description("Ask each time what action to take.")] [Description("Ask each time what action to take.")]
@ -212,91 +163,46 @@ namespace LibationFileManager
} }
[Description("When liberating books and there is an error, Libation should:")] [Description("When liberating books and there is an error, Libation should:")]
public BadBookAction BadBook public BadBookAction BadBook { get => GetNonString<BadBookAction>(); set => SetNonString(value); }
{
get
{
var badBookStr = persistentDictionary.GetString(nameof(BadBook));
return Enum.TryParse<BadBookAction>(badBookStr, out var badBookEnum) ? badBookEnum : BadBookAction.Ask;
}
set => persistentDictionary.SetString(nameof(BadBook), value.ToString());
}
[Description("Show number of newly imported titles? When unchecked, no pop-up will appear after library scan.")] [Description("Show number of newly imported titles? When unchecked, no pop-up will appear after library scan.")]
public bool ShowImportedStats public bool ShowImportedStats { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(ShowImportedStats));
set => persistentDictionary.SetNonString(nameof(ShowImportedStats), value);
}
[Description("Import episodes? (eg: podcasts) When unchecked, episodes will not be imported into Libation.")] [Description("Import episodes? (eg: podcasts) When unchecked, episodes will not be imported into Libation.")]
public bool ImportEpisodes public bool ImportEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(ImportEpisodes));
set => persistentDictionary.SetNonString(nameof(ImportEpisodes), value);
}
[Description("Download episodes? (eg: podcasts). When unchecked, episodes already in Libation will not be downloaded.")] [Description("Download episodes? (eg: podcasts). When unchecked, episodes already in Libation will not be downloaded.")]
public bool DownloadEpisodes public bool DownloadEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(DownloadEpisodes));
set => persistentDictionary.SetNonString(nameof(DownloadEpisodes), value);
}
public event EventHandler AutoScanChanged;
[Description("Automatically run periodic scans in the background?")] [Description("Automatically run periodic scans in the background?")]
public bool AutoScan public bool AutoScan { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(AutoScan));
set
{
if (AutoScan != value)
{
persistentDictionary.SetNonString(nameof(AutoScan), value);
AutoScanChanged?.Invoke(null, null);
}
}
}
[Description("Auto download books? After scan, download new books in 'checked' accounts.")] [Description("Auto download books? After scan, download new books in 'checked' accounts.")]
// poorly named setting. Should just be 'AutoDownload'. It is NOT episode specific // poorly named setting. Should just be 'AutoDownload'. It is NOT episode specific
public bool AutoDownloadEpisodes public bool AutoDownloadEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(AutoDownloadEpisodes));
set => persistentDictionary.SetNonString(nameof(AutoDownloadEpisodes), value);
}
[Description("Save all podcast episodes in a series to the series parent folder?")] [Description("Save all podcast episodes in a series to the series parent folder?")]
public bool SavePodcastsToParentFolder public bool SavePodcastsToParentFolder { get => GetNonString<bool>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<bool>(nameof(SavePodcastsToParentFolder));
set => persistentDictionary.SetNonString(nameof(SavePodcastsToParentFolder), value);
}
[Description("Global download speed limit in bytes per second.")] [Description("Global download speed limit in bytes per second.")]
public long DownloadSpeedLimit public long DownloadSpeedLimit
{ {
get get
{ {
AaxDecrypter.NetworkFileStream.GlobalSpeedLimit = persistentDictionary.GetNonString<long>(nameof(DownloadSpeedLimit)); var limit = GetNonString<long>();
return AaxDecrypter.NetworkFileStream.GlobalSpeedLimit; return limit <= 0 ? 0 : Math.Max(limit, AaxDecrypter.NetworkFileStream.MIN_BYTES_PER_SECOND);
} }
set set
{ {
AaxDecrypter.NetworkFileStream.GlobalSpeedLimit = value; var limit = value <= 0 ? 0 : Math.Max(value, AaxDecrypter.NetworkFileStream.MIN_BYTES_PER_SECOND);
persistentDictionary.SetNonString(nameof(DownloadSpeedLimit), AaxDecrypter.NetworkFileStream.GlobalSpeedLimit); SetNonString(limit);
} }
} }
#region templates: custom file naming #region templates: custom file naming
[Description("Edit how filename characters are replaced")] [Description("Edit how filename characters are replaced")]
public ReplacementCharacters ReplacementCharacters public ReplacementCharacters ReplacementCharacters { get => GetNonString<ReplacementCharacters>(); set => SetNonString(value); }
{
get => persistentDictionary.GetNonString<ReplacementCharacters>(nameof(ReplacementCharacters));
set => persistentDictionary.SetNonString(nameof(ReplacementCharacters), value);
}
[Description("How to format the folders in which files will be saved")] [Description("How to format the folders in which files will be saved")]
public string FolderTemplate public string FolderTemplate
@ -326,12 +232,12 @@ namespace LibationFileManager
set => setTemplate(nameof(ChapterTitleTemplate), Templates.ChapterTitle, value); set => setTemplate(nameof(ChapterTitleTemplate), Templates.ChapterTitle, value);
} }
private string getTemplate(string settingName, Templates templ) => templ.GetValid(persistentDictionary.GetString(settingName)); private string getTemplate(string settingName, Templates templ) => templ.GetValid(GetString(settingName));
private void setTemplate(string settingName, Templates templ, string newValue) private void setTemplate(string settingName, Templates templ, string newValue)
{ {
var template = newValue?.Trim(); var template = newValue?.Trim();
if (templ.IsValid(template)) if (templ.IsValid(template))
persistentDictionary.SetString(settingName, template); SetString(template, settingName);
} }
#endregion #endregion
} }

View File

@ -0,0 +1,155 @@
using Dinah.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace LibationFileManager
{
public partial class Configuration : INotifyPropertyChanging, INotifyPropertyChanged
{
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private readonly Dictionary<string, List<MulticastDelegate>> propertyChangedActions = new();
private readonly Dictionary<string, List<MulticastDelegate>> propertyChangingActions = new();
/// <summary>
/// Clear all subscriptions to Property<b>Changed</b> for <paramref name="propertyName"/>
/// </summary>
public void ClearChangedSubscriptions(string propertyName)
{
if (propertyChangedActions.ContainsKey(propertyName)
&& propertyChangedActions[propertyName] is not null)
propertyChangedActions[propertyName].Clear();
}
/// <summary>
/// Clear all subscriptions to Property<b>Changing</b> for <paramref name="propertyName"/>
/// </summary>
public void ClearChangingSubscriptions(string propertyName)
{
if (propertyChangingActions.ContainsKey(propertyName)
&& propertyChangingActions[propertyName] is not null)
propertyChangingActions[propertyName].Clear();
}
/// <summary>
/// Add an action to be executed when a property's value has changed
/// </summary>
/// <typeparam name="T">The <paramref name="propertyName"/>'s <see cref="Type"/></typeparam>
/// <param name="propertyName">Name of the property whose change triggers the <paramref name="action"/></param>
/// <param name="action">Action to be executed with parameters: <paramref name="propertyName"/> and <strong>NewValue</strong></param>
/// <returns>A reference to an interface that allows observers to stop receiving notifications before the provider has finished sending them.</returns>
public IDisposable SubscribeToPropertyChanged<T>(string propertyName, Action<string, T> action)
{
validateSubscriber<T>(propertyName, action);
if (!propertyChangedActions.ContainsKey(propertyName))
propertyChangedActions.Add(propertyName, new List<MulticastDelegate>());
var actionlist = propertyChangedActions[propertyName];
if (!actionlist.Contains(action))
actionlist.Add(action);
return new Unsubscriber(actionlist, action);
}
/// <summary>
/// Add an action to be executed when a property's value is changing
/// </summary>
/// <typeparam name="T">The <paramref name="propertyName"/>'s <see cref="Type"/></typeparam>
/// <param name="propertyName">Name of the property whose change triggers the <paramref name="action"/></param>
/// <param name="action">Action to be executed with parameters: <paramref name="propertyName"/>, <b>OldValue</b>, and <b>NewValue</b></param>
/// <returns>A reference to an interface that allows observers to stop receiving notifications before the provider has finished sending them.</returns>
public IDisposable SubscribeToPropertyChanging<T>(string propertyName, Action<string, T, T> action)
{
validateSubscriber<T>(propertyName, action);
if (!propertyChangingActions.ContainsKey(propertyName))
propertyChangingActions.Add(propertyName, new List<MulticastDelegate>());
var actionlist = propertyChangingActions[propertyName];
if (!actionlist.Contains(action))
actionlist.Add(action);
return new Unsubscriber(actionlist, action);
}
private void validateSubscriber<T>(string propertyName, MulticastDelegate action)
{
ArgumentValidator.EnsureNotNullOrWhiteSpace(propertyName, nameof(propertyName));
ArgumentValidator.EnsureNotNull(action, nameof(action));
var propertyInfo = GetType().GetProperty(propertyName);
if (propertyInfo is null)
throw new MissingMemberException($"{nameof(Configuration)}.{propertyName} does not exist.");
if (propertyInfo.PropertyType != typeof(T))
throw new InvalidCastException($"{nameof(Configuration)}.{propertyName} is {propertyInfo.PropertyType}, but parameter is {typeof(T)}.");
}
private void Configuration_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e is PropertyChangedEventArgsEx args && propertyChangedActions.ContainsKey(args.PropertyName))
{
foreach (var action in propertyChangedActions[args.PropertyName])
{
action.DynamicInvoke(args.PropertyName, args.NewValue);
}
}
}
private void Configuration_PropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (e is PropertyChangingEventArgsEx args && propertyChangingActions.ContainsKey(args.PropertyName))
{
foreach (var action in propertyChangingActions[args.PropertyName])
{
action.DynamicInvoke(args.PropertyName, args.OldValue, args.NewValue);
}
}
}
private class PropertyChangingEventArgsEx : PropertyChangingEventArgs
{
public object OldValue { get; }
public object NewValue { get; }
public PropertyChangingEventArgsEx(string propertyName, object oldValue, object newValue) : base(propertyName)
{
OldValue = oldValue;
NewValue = newValue;
}
}
private class PropertyChangedEventArgsEx : PropertyChangedEventArgs
{
public object NewValue { get; }
public PropertyChangedEventArgsEx(string propertyName, object newValue) : base(propertyName)
{
NewValue = newValue;
}
}
private class Unsubscriber : IDisposable
{
private List<MulticastDelegate> _observers;
private MulticastDelegate _observer;
internal Unsubscriber(List<MulticastDelegate> observers, MulticastDelegate observer)
{
_observers = observers;
_observer = observer;
}
public void Dispose()
{
if (_observers.Contains(_observer))
_observers.Remove(_observer);
}
}
}
}

View File

@ -20,10 +20,6 @@
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="OSInterop\" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType> <DebugType>embedded</DebugType>
</PropertyGroup> </PropertyGroup>

View File

@ -275,6 +275,8 @@ namespace LibationFileManager
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props, string fullDirPath, ReplacementCharacters replacements = null) public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props, string fullDirPath, ReplacementCharacters replacements = null)
{ {
if (string.IsNullOrWhiteSpace(template)) return string.Empty;
replacements ??= Configuration.Instance.ReplacementCharacters; replacements ??= Configuration.Instance.ReplacementCharacters;
var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, Path.GetExtension(props.OutputFileName)); var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, Path.GetExtension(props.OutputFileName));
@ -319,7 +321,8 @@ namespace LibationFileManager
public string GetPortionTitle(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props) public string GetPortionTitle(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props)
{ {
ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template)); if (string.IsNullOrEmpty(template)) return string.Empty;
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto)); ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
var fileNamingTemplate = new MetadataNamingTemplate(template); var fileNamingTemplate = new MetadataNamingTemplate(template);

View File

@ -80,9 +80,16 @@ namespace LibationWinForms.Dialogs
if (control is Control c) if (control is Control c)
{ {
if (c.InvokeRequired) if (c.InvokeRequired)
c.Invoke(new MethodInvoker(() => c.Enabled = enabled)); c.Invoke(new MethodInvoker(() =>
else {
c.Enabled = enabled; c.Enabled = enabled;
c.Focus();
}));
else
{
c.Enabled = enabled;
c.Focus();
}
} }
} }
@ -166,6 +173,8 @@ namespace LibationWinForms.Dialogs
private async Task saveRecords(IEnumerable<IRecord> records) private async Task saveRecords(IEnumerable<IRecord> records)
{ {
if (!records.Any()) return;
try try
{ {
var saveFileDialog = var saveFileDialog =

View File

@ -17,9 +17,19 @@ namespace LibationWinForms.Dialogs
this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio)); this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio));
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged)); this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
clipsBookmarksFormatCb.Items.AddRange(
new object[]
{
Configuration.ClipBookmarkFormat.CSV,
Configuration.ClipBookmarkFormat.Xlsx,
Configuration.ClipBookmarkFormat.Json
});
allowLibationFixupCbox.Checked = config.AllowLibationFixup; allowLibationFixupCbox.Checked = config.AllowLibationFixup;
createCueSheetCbox.Checked = config.CreateCueSheet; createCueSheetCbox.Checked = config.CreateCueSheet;
downloadCoverArtCbox.Checked = config.DownloadCoverArt; downloadCoverArtCbox.Checked = config.DownloadCoverArt;
downloadClipsBookmarksCbox.Checked = config.DownloadClipsBookmarks;
clipsBookmarksFormatCb.SelectedItem = config.ClipsBookmarksFileFormat;
retainAaxFileCbox.Checked = config.RetainAaxFile; retainAaxFileCbox.Checked = config.RetainAaxFile;
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter; splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
mergeOpeningEndCreditsCbox.Checked = config.MergeOpeningAndEndCredits; mergeOpeningEndCreditsCbox.Checked = config.MergeOpeningAndEndCredits;
@ -44,6 +54,7 @@ namespace LibationWinForms.Dialogs
convertFormatRb_CheckedChanged(this, EventArgs.Empty); convertFormatRb_CheckedChanged(this, EventArgs.Empty);
allowLibationFixupCbox_CheckedChanged(this, EventArgs.Empty); allowLibationFixupCbox_CheckedChanged(this, EventArgs.Empty);
splitFilesByChapterCbox_CheckedChanged(this, EventArgs.Empty); splitFilesByChapterCbox_CheckedChanged(this, EventArgs.Empty);
downloadClipsBookmarksCbox_CheckedChanged(this, EventArgs.Empty);
} }
private void Save_AudioSettings(Configuration config) private void Save_AudioSettings(Configuration config)
@ -51,6 +62,8 @@ namespace LibationWinForms.Dialogs
config.AllowLibationFixup = allowLibationFixupCbox.Checked; config.AllowLibationFixup = allowLibationFixupCbox.Checked;
config.CreateCueSheet = createCueSheetCbox.Checked; config.CreateCueSheet = createCueSheetCbox.Checked;
config.DownloadCoverArt = downloadCoverArtCbox.Checked; config.DownloadCoverArt = downloadCoverArtCbox.Checked;
config.DownloadClipsBookmarks = downloadClipsBookmarksCbox.Checked;
config.ClipsBookmarksFileFormat = (Configuration.ClipBookmarkFormat)clipsBookmarksFormatCb.SelectedItem;
config.RetainAaxFile = retainAaxFileCbox.Checked; config.RetainAaxFile = retainAaxFileCbox.Checked;
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked; config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
config.MergeOpeningAndEndCredits = mergeOpeningEndCreditsCbox.Checked; config.MergeOpeningAndEndCredits = mergeOpeningEndCreditsCbox.Checked;
@ -68,6 +81,12 @@ namespace LibationWinForms.Dialogs
config.ChapterTitleTemplate = chapterTitleTemplateTb.Text; config.ChapterTitleTemplate = chapterTitleTemplateTb.Text;
} }
private void downloadClipsBookmarksCbox_CheckedChanged(object sender, EventArgs e)
{
clipsBookmarksFormatCb.Enabled = downloadClipsBookmarksCbox.Checked;
}
private void lameTargetRb_CheckedChanged(object sender, EventArgs e) private void lameTargetRb_CheckedChanged(object sender, EventArgs e)
{ {
lameBitrateGb.Enabled = lameTargetBitrateRb.Checked; lameBitrateGb.Enabled = lameTargetBitrateRb.Checked;

View File

@ -73,6 +73,8 @@
this.folderTemplateTb = new System.Windows.Forms.TextBox(); this.folderTemplateTb = new System.Windows.Forms.TextBox();
this.folderTemplateLbl = new System.Windows.Forms.Label(); this.folderTemplateLbl = new System.Windows.Forms.Label();
this.tab4AudioFileOptions = new System.Windows.Forms.TabPage(); this.tab4AudioFileOptions = new System.Windows.Forms.TabPage();
this.clipsBookmarksFormatCb = new System.Windows.Forms.ComboBox();
this.downloadClipsBookmarksCbox = new System.Windows.Forms.CheckBox();
this.audiobookFixupsGb = new System.Windows.Forms.GroupBox(); this.audiobookFixupsGb = new System.Windows.Forms.GroupBox();
this.stripUnabridgedCbox = new System.Windows.Forms.CheckBox(); this.stripUnabridgedCbox = new System.Windows.Forms.CheckBox();
this.chapterTitleTemplateGb = new System.Windows.Forms.GroupBox(); this.chapterTitleTemplateGb = new System.Windows.Forms.GroupBox();
@ -281,7 +283,7 @@
this.allowLibationFixupCbox.AutoSize = true; this.allowLibationFixupCbox.AutoSize = true;
this.allowLibationFixupCbox.Checked = true; this.allowLibationFixupCbox.Checked = true;
this.allowLibationFixupCbox.CheckState = System.Windows.Forms.CheckState.Checked; this.allowLibationFixupCbox.CheckState = System.Windows.Forms.CheckState.Checked;
this.allowLibationFixupCbox.Location = new System.Drawing.Point(19, 118); this.allowLibationFixupCbox.Location = new System.Drawing.Point(19, 143);
this.allowLibationFixupCbox.Name = "allowLibationFixupCbox"; this.allowLibationFixupCbox.Name = "allowLibationFixupCbox";
this.allowLibationFixupCbox.Size = new System.Drawing.Size(163, 19); this.allowLibationFixupCbox.Size = new System.Drawing.Size(163, 19);
this.allowLibationFixupCbox.TabIndex = 10; this.allowLibationFixupCbox.TabIndex = 10;
@ -633,6 +635,8 @@
// //
// tab4AudioFileOptions // tab4AudioFileOptions
// //
this.tab4AudioFileOptions.Controls.Add(this.clipsBookmarksFormatCb);
this.tab4AudioFileOptions.Controls.Add(this.downloadClipsBookmarksCbox);
this.tab4AudioFileOptions.Controls.Add(this.audiobookFixupsGb); this.tab4AudioFileOptions.Controls.Add(this.audiobookFixupsGb);
this.tab4AudioFileOptions.Controls.Add(this.chapterTitleTemplateGb); this.tab4AudioFileOptions.Controls.Add(this.chapterTitleTemplateGb);
this.tab4AudioFileOptions.Controls.Add(this.lameOptionsGb); this.tab4AudioFileOptions.Controls.Add(this.lameOptionsGb);
@ -649,6 +653,26 @@
this.tab4AudioFileOptions.Text = "Audio File Options"; this.tab4AudioFileOptions.Text = "Audio File Options";
this.tab4AudioFileOptions.UseVisualStyleBackColor = true; this.tab4AudioFileOptions.UseVisualStyleBackColor = true;
// //
// clipsBookmarksFormatCb
//
this.clipsBookmarksFormatCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.clipsBookmarksFormatCb.FormattingEnabled = true;
this.clipsBookmarksFormatCb.Location = new System.Drawing.Point(269, 64);
this.clipsBookmarksFormatCb.Name = "clipsBookmarksFormatCb";
this.clipsBookmarksFormatCb.Size = new System.Drawing.Size(67, 23);
this.clipsBookmarksFormatCb.TabIndex = 21;
//
// downloadClipsBookmarksCbox
//
this.downloadClipsBookmarksCbox.AutoSize = true;
this.downloadClipsBookmarksCbox.Location = new System.Drawing.Point(19, 68);
this.downloadClipsBookmarksCbox.Name = "downloadClipsBookmarksCbox";
this.downloadClipsBookmarksCbox.Size = new System.Drawing.Size(248, 19);
this.downloadClipsBookmarksCbox.TabIndex = 20;
this.downloadClipsBookmarksCbox.Text = "Download Clips, Notes, and Bookmarks as";
this.downloadClipsBookmarksCbox.UseVisualStyleBackColor = true;
this.downloadClipsBookmarksCbox.CheckedChanged += new System.EventHandler(this.downloadClipsBookmarksCbox_CheckedChanged);
//
// audiobookFixupsGb // audiobookFixupsGb
// //
this.audiobookFixupsGb.Controls.Add(this.splitFilesByChapterCbox); this.audiobookFixupsGb.Controls.Add(this.splitFilesByChapterCbox);
@ -656,7 +680,7 @@
this.audiobookFixupsGb.Controls.Add(this.convertLosslessRb); this.audiobookFixupsGb.Controls.Add(this.convertLosslessRb);
this.audiobookFixupsGb.Controls.Add(this.convertLossyRb); this.audiobookFixupsGb.Controls.Add(this.convertLossyRb);
this.audiobookFixupsGb.Controls.Add(this.stripAudibleBrandingCbox); this.audiobookFixupsGb.Controls.Add(this.stripAudibleBrandingCbox);
this.audiobookFixupsGb.Location = new System.Drawing.Point(6, 143); this.audiobookFixupsGb.Location = new System.Drawing.Point(6, 169);
this.audiobookFixupsGb.Name = "audiobookFixupsGb"; this.audiobookFixupsGb.Name = "audiobookFixupsGb";
this.audiobookFixupsGb.Size = new System.Drawing.Size(403, 160); this.audiobookFixupsGb.Size = new System.Drawing.Size(403, 160);
this.audiobookFixupsGb.TabIndex = 19; this.audiobookFixupsGb.TabIndex = 19;
@ -1032,7 +1056,7 @@
// mergeOpeningEndCreditsCbox // mergeOpeningEndCreditsCbox
// //
this.mergeOpeningEndCreditsCbox.AutoSize = true; this.mergeOpeningEndCreditsCbox.AutoSize = true;
this.mergeOpeningEndCreditsCbox.Location = new System.Drawing.Point(19, 93); this.mergeOpeningEndCreditsCbox.Location = new System.Drawing.Point(19, 118);
this.mergeOpeningEndCreditsCbox.Name = "mergeOpeningEndCreditsCbox"; this.mergeOpeningEndCreditsCbox.Name = "mergeOpeningEndCreditsCbox";
this.mergeOpeningEndCreditsCbox.Size = new System.Drawing.Size(198, 19); this.mergeOpeningEndCreditsCbox.Size = new System.Drawing.Size(198, 19);
this.mergeOpeningEndCreditsCbox.TabIndex = 13; this.mergeOpeningEndCreditsCbox.TabIndex = 13;
@ -1042,7 +1066,7 @@
// retainAaxFileCbox // retainAaxFileCbox
// //
this.retainAaxFileCbox.AutoSize = true; this.retainAaxFileCbox.AutoSize = true;
this.retainAaxFileCbox.Location = new System.Drawing.Point(19, 68); this.retainAaxFileCbox.Location = new System.Drawing.Point(19, 93);
this.retainAaxFileCbox.Name = "retainAaxFileCbox"; this.retainAaxFileCbox.Name = "retainAaxFileCbox";
this.retainAaxFileCbox.Size = new System.Drawing.Size(132, 19); this.retainAaxFileCbox.Size = new System.Drawing.Size(132, 19);
this.retainAaxFileCbox.TabIndex = 10; this.retainAaxFileCbox.TabIndex = 10;
@ -1214,5 +1238,7 @@
private System.Windows.Forms.GroupBox audiobookFixupsGb; private System.Windows.Forms.GroupBox audiobookFixupsGb;
private System.Windows.Forms.CheckBox betaOptInCbox; private System.Windows.Forms.CheckBox betaOptInCbox;
private System.Windows.Forms.CheckBox useCoverAsFolderIconCb; private System.Windows.Forms.CheckBox useCoverAsFolderIconCb;
private System.Windows.Forms.ComboBox clipsBookmarksFormatCb;
private System.Windows.Forms.CheckBox downloadClipsBookmarksCbox;
} }
} }

View File

@ -78,7 +78,7 @@ namespace LibationWinForms
private void ToggleQueueHideBtn_Click(object sender, EventArgs e) private void ToggleQueueHideBtn_Click(object sender, EventArgs e)
{ {
SetQueueCollapseState(!splitContainer1.Panel2Collapsed); SetQueueCollapseState(!splitContainer1.Panel2Collapsed);
Configuration.Instance.SetObject(nameof(splitContainer1.Panel2Collapsed), splitContainer1.Panel2Collapsed); Configuration.Instance.SetNonString(splitContainer1.Panel2Collapsed, nameof(splitContainer1.Panel2Collapsed));
} }
private void ProcessBookQueue1_PopOut(object sender, EventArgs e) private void ProcessBookQueue1_PopOut(object sender, EventArgs e)

View File

@ -56,9 +56,17 @@ namespace LibationWinForms
AccountsSettingsPersister.Saving += accountsPreSave; AccountsSettingsPersister.Saving += accountsPreSave;
AccountsSettingsPersister.Saved += accountsPostSave; AccountsSettingsPersister.Saved += accountsPostSave;
Configuration.Instance.PropertyChanged += Configuration_PropertyChanged;
}
private void Configuration_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Configuration.Instance.AutoScan))
{
// when autoscan setting is changed, update menu checkbox and run autoscan // when autoscan setting is changed, update menu checkbox and run autoscan
Configuration.Instance.AutoScanChanged += updateAutoScanLibraryToolStripMenuItem; updateAutoScanLibraryToolStripMenuItem(sender, e);
Configuration.Instance.AutoScanChanged += startAutoScan; startAutoScan(sender, e);
}
} }
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts; private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;

View File

@ -87,7 +87,7 @@ namespace LibationWinForms
saveState.IsMaximized = form.WindowState == FormWindowState.Maximized; saveState.IsMaximized = form.WindowState == FormWindowState.Maximized;
config.SetObject(form.Name, saveState); config.SetNonString(saveState, form.Name);
} }
} }

View File

@ -71,6 +71,15 @@ namespace LibationWinForms.GridView
&& updateReviewTask?.IsCompleted is not false) && updateReviewTask?.IsCompleted is not false)
{ {
updateReviewTask = UpdateRating(value); updateReviewTask = UpdateRating(value);
updateReviewTask.ContinueWith(t =>
{
if (t.Result)
{
_myRating = value;
LibraryBook.Book.UpdateUserDefinedItem(Book.UserDefinedItem.Tags, Book.UserDefinedItem.BookStatus, Book.UserDefinedItem.PdfStatus, value);
}
NotifyPropertyChanged();
});
} }
} }
} }
@ -80,18 +89,12 @@ namespace LibationWinForms.GridView
#region User rating #region User rating
private Task updateReviewTask; private Task<bool> updateReviewTask;
private async Task UpdateRating(Rating rating) private async Task<bool> UpdateRating(Rating rating)
{ {
var api = await LibraryBook.GetApiAsync(); var api = await LibraryBook.GetApiAsync();
if (await api.ReviewAsync(Book.AudibleProductId, (int)rating.OverallRating, (int)rating.PerformanceRating, (int)rating.StoryRating)) return await api.ReviewAsync(Book.AudibleProductId, (int)rating.OverallRating, (int)rating.PerformanceRating, (int)rating.StoryRating);
{
_myRating = rating;
LibraryBook.Book.UpdateUserDefinedItem(Book.UserDefinedItem.Tags, Book.UserDefinedItem.BookStatus, Book.UserDefinedItem.PdfStatus, rating);
}
this.NotifyPropertyChanged(nameof(MyRating));
} }
#endregion #endregion