Add Configurations property change notifications
This commit is contained in:
parent
f09baa1318
commit
1f7000c2c9
@ -43,7 +43,21 @@ namespace AaxDecrypter
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//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");
|
||||
if (await Task.Run(Step_Cleanup))
|
||||
Serilog.Log.Information("Completed Cleanup");
|
||||
|
||||
@ -49,6 +49,19 @@ namespace AaxDecrypter
|
||||
}
|
||||
|
||||
//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");
|
||||
if (await Task.Run(Step_Cleanup))
|
||||
Serilog.Log.Information("Completed Step 4: Cleanup");
|
||||
|
||||
@ -48,11 +48,18 @@ namespace AaxDecrypter
|
||||
TempFilePath = Path.ChangeExtension(jsonDownloadState, ".aaxc");
|
||||
|
||||
DownloadOptions = ArgumentValidator.EnsureNotNull(dlOptions, nameof(dlOptions));
|
||||
DownloadOptions.PropertyChanged += DownloadOptions_PropertyChanged;
|
||||
|
||||
// delete file after validation is complete
|
||||
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 virtual void SetCoverArt(byte[] coverArt)
|
||||
@ -132,14 +139,27 @@ namespace AaxDecrypter
|
||||
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()
|
||||
{
|
||||
if (!File.Exists(jsonDownloadState))
|
||||
return NewNetworkFilePersister();
|
||||
|
||||
NetworkFileStreamPersister nfsp = default;
|
||||
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.
|
||||
// The new url will be to the same file.
|
||||
nfsp.NetworkFileStream.SetUriForSameFile(new Uri(DownloadOptions.DownloadUrl));
|
||||
@ -149,7 +169,12 @@ namespace AaxDecrypter
|
||||
{
|
||||
FileUtility.SaferDelete(jsonDownloadState);
|
||||
FileUtility.SaferDelete(TempFilePath);
|
||||
return NewNetworkFilePersister();
|
||||
return nfsp = NewNetworkFilePersister();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (nfsp is not null)
|
||||
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
using AAXClean;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
public interface IDownloadOptions
|
||||
public interface IDownloadOptions : INotifyPropertyChanged
|
||||
{
|
||||
FileManager.ReplacementCharacters ReplacementCharacters { get; }
|
||||
string DownloadUrl { get; }
|
||||
@ -14,6 +16,8 @@ namespace AaxDecrypter
|
||||
bool RetainEncryptedFile { get; }
|
||||
bool StripUnabridged { get; }
|
||||
bool CreateCueSheet { get; }
|
||||
bool DownloadClipsBookmarks { get; }
|
||||
long DownloadSpeedBps { get; }
|
||||
ChapterInfo ChapterInfo { get; }
|
||||
bool FixupFile { get; }
|
||||
NAudio.Lame.LameConfig LameConfig { get; }
|
||||
@ -21,5 +25,6 @@ namespace AaxDecrypter
|
||||
bool MatchSourceBitrate { get; }
|
||||
string GetMultipartFileName(MultiConvertFileProperties props);
|
||||
string GetMultipartTitleName(MultiConvertFileProperties props);
|
||||
Task<string> SaveClipsAndBookmarks(string fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,9 +41,9 @@ namespace AaxDecrypter
|
||||
[JsonIgnore]
|
||||
public bool IsCancelled => _cancellationSource.IsCancellationRequested;
|
||||
|
||||
private static long _globalSpeedLimit = 0;
|
||||
private long _speedLimit = 0;
|
||||
/// <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
|
||||
|
||||
@ -70,7 +70,7 @@ namespace AaxDecrypter
|
||||
|
||||
//Minimum throttle rate. The minimum amount of data that can be throttled
|
||||
//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
|
||||
|
||||
@ -202,7 +202,7 @@ namespace AaxDecrypter
|
||||
|
||||
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;
|
||||
if (delayMS > 0)
|
||||
|
||||
@ -16,7 +16,7 @@ namespace AaxDecrypter
|
||||
{
|
||||
try
|
||||
{
|
||||
Serilog.Log.Information("Begin download and convert Aaxc To {format}", DownloadOptions.OutputFormat);
|
||||
Serilog.Log.Information("Begin downloading unencrypted audiobook.");
|
||||
|
||||
//Step 1
|
||||
Serilog.Log.Information("Begin Step 1: Get Mp3 Metadata");
|
||||
@ -39,6 +39,19 @@ namespace AaxDecrypter
|
||||
}
|
||||
|
||||
//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");
|
||||
if (await Task.Run(Step_Cleanup))
|
||||
Serilog.Log.Information("Completed Step 3: Cleanup");
|
||||
@ -58,7 +71,6 @@ namespace AaxDecrypter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override Task CancelAsync()
|
||||
{
|
||||
IsCanceled = true;
|
||||
|
||||
@ -174,6 +174,12 @@ namespace AppScaffolding
|
||||
if (!config.Exists(nameof(config.DownloadCoverArt)))
|
||||
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)))
|
||||
config.AutoDownloadEpisodes = false;
|
||||
|
||||
@ -229,7 +235,7 @@ namespace AppScaffolding
|
||||
{ "Using", new JArray{ "Dinah.Core", "Serilog.Exceptions" } }, // dll's name, NOT namespace
|
||||
{ "Enrich", new JArray{ "WithCaller", "WithExceptionDetails" } },
|
||||
};
|
||||
config.SetObject("Serilog", serilogObj);
|
||||
config.SetNonString(serilogObj, "Serilog");
|
||||
}
|
||||
|
||||
// to restore original: Console.SetOut(origOut);
|
||||
|
||||
@ -104,7 +104,7 @@ namespace FileLiberator
|
||||
|
||||
var api = await libraryBook.GetApiAsync();
|
||||
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 cacheDir = AudibleFileStorage.DownloadsInProgressDirectory;
|
||||
@ -133,9 +133,7 @@ namespace FileLiberator
|
||||
abDownloader.FileCreated += (_, path) => OnFileCreated(libraryBook, path);
|
||||
|
||||
// REAL WORK DONE HERE
|
||||
var success = await abDownloader.RunAsync();
|
||||
|
||||
return success;
|
||||
return await abDownloader.RunAsync();
|
||||
}
|
||||
|
||||
private DownloadOptions BuildDownloadOptions(LibraryBook libraryBook, Configuration config, AudibleApi.Common.ContentLicense contentLic)
|
||||
@ -168,6 +166,8 @@ namespace FileLiberator
|
||||
Downsample = config.AllowLibationFixup && config.LameDownsampleMono,
|
||||
MatchSourceBitrate = config.AllowLibationFixup && config.LameMatchSourceBR && config.LameTargetBitrate,
|
||||
CreateCueSheet = config.CreateCueSheet,
|
||||
DownloadClipsBookmarks = config.DownloadClipsBookmarks,
|
||||
DownloadSpeedBps = config.DownloadSpeedLimit,
|
||||
LameConfig = GetLameOptions(config),
|
||||
ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(chapterStartMs)),
|
||||
FixupFile = config.AllowLibationFixup
|
||||
|
||||
@ -4,11 +4,20 @@ using Dinah.Core;
|
||||
using DataLayer;
|
||||
using LibationFileManager;
|
||||
using FileManager;
|
||||
using System.Threading.Tasks;
|
||||
using System.ComponentModel;
|
||||
using System;
|
||||
using System.IO;
|
||||
using ApplicationServices;
|
||||
|
||||
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 string DownloadUrl { get; }
|
||||
public string UserAgent { get; }
|
||||
@ -19,6 +28,8 @@ namespace FileLiberator
|
||||
public bool RetainEncryptedFile { get; init; }
|
||||
public bool StripUnabridged { get; init; }
|
||||
public bool CreateCueSheet { get; init; }
|
||||
public bool DownloadClipsBookmarks { get; init; }
|
||||
public long DownloadSpeedBps { get; set; }
|
||||
public ChapterInfo ChapterInfo { get; init; }
|
||||
public bool FixupFile { get; init; }
|
||||
public NAudio.Lame.LameConfig LameConfig { get; init; }
|
||||
@ -32,14 +43,55 @@ namespace FileLiberator
|
||||
public string GetMultipartTitleName(MultiConvertFileProperties 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)
|
||||
{
|
||||
LibraryBookDto = ArgumentValidator
|
||||
.EnsureNotNull(libraryBook, nameof(libraryBook))
|
||||
.ToDto();
|
||||
LibraryBook = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook));
|
||||
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,18 @@ namespace FileManager
|
||||
{
|
||||
var obj = GetObject(propertyName);
|
||||
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>();
|
||||
return (T)obj;
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ namespace LibationAvalonia
|
||||
saveState.Width = (int)form.Bounds.Size.Width;
|
||||
saveState.Height = (int)form.Bounds.Size.Height;
|
||||
|
||||
config.SetObject(form.GetType().Name, saveState);
|
||||
config.SetNonString(saveState, form.GetType().Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -56,7 +56,7 @@ namespace LibationAvalonia.Views
|
||||
public void ToggleQueueHideBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
SetQueueCollapseState(_viewModel.QueueOpen);
|
||||
Configuration.Instance.SetObject(nameof(_viewModel.QueueOpen), _viewModel.QueueOpen);
|
||||
Configuration.Instance.SetNonString(_viewModel.QueueOpen, nameof(_viewModel.QueueOpen));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
@ -13,6 +14,7 @@ namespace LibationAvalonia.Views
|
||||
public partial class MainWindow
|
||||
{
|
||||
private InterruptableTimer autoScanTimer;
|
||||
private IDisposable cancellation;
|
||||
|
||||
private void Configure_ScanAuto()
|
||||
{
|
||||
@ -53,7 +55,11 @@ namespace LibationAvalonia.Views
|
||||
AccountsSettingsPersister.Saved += accountsPostSave;
|
||||
|
||||
// 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;
|
||||
|
||||
@ -97,13 +97,13 @@ namespace LibationAvalonia.Views
|
||||
const string ignoreUpdate = "IgnoreUpdate";
|
||||
var config = Configuration.Instance;
|
||||
|
||||
if (config.GetObject(ignoreUpdate)?.ToString() == upgradeProperties.LatestRelease.ToString())
|
||||
if (config.GetString(ignoreUpdate) == upgradeProperties.LatestRelease.ToString())
|
||||
return;
|
||||
|
||||
var notificationResult = await new UpgradeNotification(upgradeProperties).ShowDialog<DialogResult>(this);
|
||||
|
||||
if (notificationResult == DialogResult.Ignore)
|
||||
config.SetObject(ignoreUpdate, upgradeProperties.LatestRelease.ToString());
|
||||
config.SetString(upgradeProperties.LatestRelease.ToString(), ignoreUpdate);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@ -36,6 +36,7 @@ namespace LibationFileManager
|
||||
}
|
||||
set
|
||||
{
|
||||
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(nameof(LogLevel), LogLevel, value));
|
||||
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
|
||||
if (!valueWasChanged)
|
||||
{
|
||||
@ -45,6 +46,8 @@ namespace LibationFileManager
|
||||
|
||||
configuration.Reload();
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(nameof(LogLevel), value));
|
||||
|
||||
Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new
|
||||
{
|
||||
LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),
|
||||
|
||||
@ -3,7 +3,10 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FileManager;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
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
|
||||
// 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 object GetObject(string propertyName) => persistentDictionary.GetObject(propertyName);
|
||||
public void SetObject(string propertyName, object newValue) => persistentDictionary.SetNonString(propertyName, newValue);
|
||||
public T GetNonString<T>([CallerMemberName] string propertyName = "") => persistentDictionary.GetNonString<T>(propertyName);
|
||||
public object GetObject([CallerMemberName] string propertyName = "") => persistentDictionary.GetObject(propertyName);
|
||||
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>
|
||||
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);
|
||||
|
||||
[Description("Set cover art as the folder's icon. (Windows only)")]
|
||||
public bool UseCoverAsFolderIcon
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(UseCoverAsFolderIcon));
|
||||
set => persistentDictionary.SetNonString(nameof(UseCoverAsFolderIcon), value);
|
||||
}
|
||||
public bool UseCoverAsFolderIcon { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[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
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(BetaOptIn));
|
||||
set => persistentDictionary.SetNonString(nameof(BetaOptIn), value);
|
||||
}
|
||||
public bool BetaOptIn { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Location for book storage. Includes destination of newly liberated books")]
|
||||
public string Books
|
||||
{
|
||||
get => persistentDictionary.GetString(nameof(Books));
|
||||
set => persistentDictionary.SetString(nameof(Books), value);
|
||||
}
|
||||
public string Books { get => GetString(); set => SetString(value); }
|
||||
|
||||
// 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")]
|
||||
public string InProgress
|
||||
{
|
||||
get => persistentDictionary.GetString(nameof(InProgress));
|
||||
set => persistentDictionary.SetString(nameof(InProgress), value);
|
||||
}
|
||||
public string InProgress { get => GetString(); set => SetString(value); }
|
||||
|
||||
[Description("Allow Libation to fix up audiobook metadata")]
|
||||
public bool AllowLibationFixup
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(AllowLibationFixup));
|
||||
set => persistentDictionary.SetNonString(nameof(AllowLibationFixup), value);
|
||||
}
|
||||
public bool AllowLibationFixup { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Create a cue sheet (.cue)")]
|
||||
public bool CreateCueSheet
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(CreateCueSheet));
|
||||
set => persistentDictionary.SetNonString(nameof(CreateCueSheet), value);
|
||||
}
|
||||
public bool CreateCueSheet { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Retain the Aax file after successfully decrypting")]
|
||||
public bool RetainAaxFile
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(RetainAaxFile));
|
||||
set => persistentDictionary.SetNonString(nameof(RetainAaxFile), value);
|
||||
}
|
||||
public bool RetainAaxFile { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Split my books into multiple files by chapter")]
|
||||
public bool SplitFilesByChapter
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(SplitFilesByChapter));
|
||||
set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value);
|
||||
}
|
||||
public bool SplitFilesByChapter { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Merge Opening/End Credits into the following/preceding chapters")]
|
||||
public bool MergeOpeningAndEndCredits
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(MergeOpeningAndEndCredits));
|
||||
set => persistentDictionary.SetNonString(nameof(MergeOpeningAndEndCredits), value);
|
||||
}
|
||||
public bool MergeOpeningAndEndCredits { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Strip \"(Unabridged)\" from audiobook metadata tags")]
|
||||
public bool StripUnabridged
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(StripUnabridged));
|
||||
set => persistentDictionary.SetNonString(nameof(StripUnabridged), value);
|
||||
}
|
||||
public bool StripUnabridged { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Strip audible branding from the start and end of audiobooks.\r\n(e.g. \"This is Audible\")")]
|
||||
public bool StripAudibleBrandAudio
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(StripAudibleBrandAudio));
|
||||
set => persistentDictionary.SetNonString(nameof(StripAudibleBrandAudio), value);
|
||||
}
|
||||
public bool StripAudibleBrandAudio { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Decrypt to lossy format?")]
|
||||
public bool DecryptToLossy
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(DecryptToLossy));
|
||||
set => persistentDictionary.SetNonString(nameof(DecryptToLossy), value);
|
||||
}
|
||||
public bool DecryptToLossy { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Lame encoder target. true = Bitrate, false = Quality")]
|
||||
public bool LameTargetBitrate
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(LameTargetBitrate));
|
||||
set => persistentDictionary.SetNonString(nameof(LameTargetBitrate), value);
|
||||
}
|
||||
public bool LameTargetBitrate { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Lame encoder downsamples to mono")]
|
||||
public bool LameDownsampleMono
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(LameDownsampleMono));
|
||||
set => persistentDictionary.SetNonString(nameof(LameDownsampleMono), value);
|
||||
}
|
||||
public bool LameDownsampleMono { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Lame target bitrate [16,320]")]
|
||||
public int LameBitrate
|
||||
{
|
||||
get => persistentDictionary.GetNonString<int>(nameof(LameBitrate));
|
||||
set => persistentDictionary.SetNonString(nameof(LameBitrate), value);
|
||||
}
|
||||
public int LameBitrate { get => GetNonString<int>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Restrict encoder to constant bitrate?")]
|
||||
public bool LameConstantBitrate
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(LameConstantBitrate));
|
||||
set => persistentDictionary.SetNonString(nameof(LameConstantBitrate), value);
|
||||
}
|
||||
public bool LameConstantBitrate { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Match the source bitrate?")]
|
||||
public bool LameMatchSourceBR
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(LameMatchSourceBR));
|
||||
set => persistentDictionary.SetNonString(nameof(LameMatchSourceBR), value);
|
||||
}
|
||||
public bool LameMatchSourceBR { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Lame target VBR quality [10,100]")]
|
||||
public int LameVBRQuality
|
||||
{
|
||||
get => persistentDictionary.GetNonString<int>(nameof(LameVBRQuality));
|
||||
set => persistentDictionary.SetNonString(nameof(LameVBRQuality), value);
|
||||
}
|
||||
public int LameVBRQuality { get => GetNonString<int>(); set => SetNonString(value); }
|
||||
|
||||
[Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
|
||||
public Dictionary<string, bool> GridColumnsVisibilities
|
||||
{
|
||||
get => persistentDictionary.GetNonString<Dictionary<string, bool>>(nameof(GridColumnsVisibilities));
|
||||
set => persistentDictionary.SetNonString(nameof(GridColumnsVisibilities), value);
|
||||
}
|
||||
public Dictionary<string, bool> GridColumnsVisibilities { get => GetNonString<Dictionary<string, bool>>(); set => SetNonString(value); }
|
||||
|
||||
[Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")]
|
||||
public Dictionary<string, int> GridColumnsDisplayIndices
|
||||
{
|
||||
get => persistentDictionary.GetNonString<Dictionary<string, int>>(nameof(GridColumnsDisplayIndices));
|
||||
set => persistentDictionary.SetNonString(nameof(GridColumnsDisplayIndices), value);
|
||||
}
|
||||
public Dictionary<string, int> GridColumnsDisplayIndices { get => GetNonString<Dictionary<string, int>>(); set => SetNonString(value); }
|
||||
|
||||
[Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")]
|
||||
public Dictionary<string, int> GridColumnsWidths
|
||||
{
|
||||
get => persistentDictionary.GetNonString<Dictionary<string, int>>(nameof(GridColumnsWidths));
|
||||
set => persistentDictionary.SetNonString(nameof(GridColumnsWidths), value);
|
||||
}
|
||||
public Dictionary<string, int> GridColumnsWidths { get => GetNonString<Dictionary<string, int>>(); set => SetNonString(value); }
|
||||
|
||||
[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));
|
||||
set => persistentDictionary.SetNonString(nameof(DownloadCoverArt), value);
|
||||
[Description("Comma-separated values")]
|
||||
CSV,
|
||||
[Description("Microsoft Excel Spreadsheet")]
|
||||
Xlsx,
|
||||
[Description("JavaScript Object Notation (JSON)")]
|
||||
Json
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum BadBookAction
|
||||
{
|
||||
[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:")]
|
||||
public BadBookAction BadBook
|
||||
{
|
||||
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());
|
||||
}
|
||||
public BadBookAction BadBook { get => GetNonString<BadBookAction>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Show number of newly imported titles? When unchecked, no pop-up will appear after library scan.")]
|
||||
public bool ShowImportedStats
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(ShowImportedStats));
|
||||
set => persistentDictionary.SetNonString(nameof(ShowImportedStats), value);
|
||||
}
|
||||
public bool ShowImportedStats { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Import episodes? (eg: podcasts) When unchecked, episodes will not be imported into Libation.")]
|
||||
public bool ImportEpisodes
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(ImportEpisodes));
|
||||
set => persistentDictionary.SetNonString(nameof(ImportEpisodes), value);
|
||||
}
|
||||
public bool ImportEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Download episodes? (eg: podcasts). When unchecked, episodes already in Libation will not be downloaded.")]
|
||||
public bool DownloadEpisodes
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(DownloadEpisodes));
|
||||
set => persistentDictionary.SetNonString(nameof(DownloadEpisodes), value);
|
||||
}
|
||||
|
||||
public event EventHandler AutoScanChanged;
|
||||
public bool DownloadEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Automatically run periodic scans in the background?")]
|
||||
public bool AutoScan
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(AutoScan));
|
||||
set
|
||||
{
|
||||
if (AutoScan != value)
|
||||
{
|
||||
persistentDictionary.SetNonString(nameof(AutoScan), value);
|
||||
AutoScanChanged?.Invoke(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool AutoScan { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Auto download books? After scan, download new books in 'checked' accounts.")]
|
||||
// poorly named setting. Should just be 'AutoDownload'. It is NOT episode specific
|
||||
public bool AutoDownloadEpisodes
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(AutoDownloadEpisodes));
|
||||
set => persistentDictionary.SetNonString(nameof(AutoDownloadEpisodes), value);
|
||||
}
|
||||
public bool AutoDownloadEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Save all podcast episodes in a series to the series parent folder?")]
|
||||
public bool SavePodcastsToParentFolder
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(SavePodcastsToParentFolder));
|
||||
set => persistentDictionary.SetNonString(nameof(SavePodcastsToParentFolder), value);
|
||||
}
|
||||
public bool SavePodcastsToParentFolder { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||
|
||||
[Description("Global download speed limit in bytes per second.")]
|
||||
public long DownloadSpeedLimit
|
||||
{
|
||||
get
|
||||
{
|
||||
AaxDecrypter.NetworkFileStream.GlobalSpeedLimit = persistentDictionary.GetNonString<long>(nameof(DownloadSpeedLimit));
|
||||
return AaxDecrypter.NetworkFileStream.GlobalSpeedLimit;
|
||||
var limit = GetNonString<long>();
|
||||
return limit <= 0 ? 0 : Math.Max(limit, AaxDecrypter.NetworkFileStream.MIN_BYTES_PER_SECOND);
|
||||
}
|
||||
set
|
||||
{
|
||||
AaxDecrypter.NetworkFileStream.GlobalSpeedLimit = value;
|
||||
persistentDictionary.SetNonString(nameof(DownloadSpeedLimit), AaxDecrypter.NetworkFileStream.GlobalSpeedLimit);
|
||||
var limit = value <= 0 ? 0 : Math.Max(value, AaxDecrypter.NetworkFileStream.MIN_BYTES_PER_SECOND);
|
||||
SetNonString(limit);
|
||||
}
|
||||
}
|
||||
|
||||
#region templates: custom file naming
|
||||
|
||||
[Description("Edit how filename characters are replaced")]
|
||||
public ReplacementCharacters ReplacementCharacters
|
||||
{
|
||||
get => persistentDictionary.GetNonString<ReplacementCharacters>(nameof(ReplacementCharacters));
|
||||
set => persistentDictionary.SetNonString(nameof(ReplacementCharacters), value);
|
||||
}
|
||||
public ReplacementCharacters ReplacementCharacters { get => GetNonString<ReplacementCharacters>(); set => SetNonString(value); }
|
||||
|
||||
[Description("How to format the folders in which files will be saved")]
|
||||
public string FolderTemplate
|
||||
@ -326,12 +232,12 @@ namespace LibationFileManager
|
||||
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)
|
||||
{
|
||||
var template = newValue?.Trim();
|
||||
if (templ.IsValid(template))
|
||||
persistentDictionary.SetString(settingName, template);
|
||||
SetString(template, settingName);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
155
Source/LibationFileManager/Configuration.PropertyChange.cs
Normal file
155
Source/LibationFileManager/Configuration.PropertyChange.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,10 +20,6 @@
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="OSInterop\" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
@ -275,6 +275,8 @@ namespace LibationFileManager
|
||||
|
||||
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;
|
||||
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)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
|
||||
if (string.IsNullOrEmpty(template)) return string.Empty;
|
||||
|
||||
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
||||
|
||||
var fileNamingTemplate = new MetadataNamingTemplate(template);
|
||||
|
||||
@ -80,9 +80,16 @@ namespace LibationWinForms.Dialogs
|
||||
if (control is Control c)
|
||||
{
|
||||
if (c.InvokeRequired)
|
||||
c.Invoke(new MethodInvoker(() => c.Enabled = enabled));
|
||||
else
|
||||
c.Invoke(new MethodInvoker(() =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (!records.Any()) return;
|
||||
|
||||
try
|
||||
{
|
||||
var saveFileDialog =
|
||||
|
||||
@ -17,9 +17,19 @@ namespace LibationWinForms.Dialogs
|
||||
this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio));
|
||||
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;
|
||||
createCueSheetCbox.Checked = config.CreateCueSheet;
|
||||
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
|
||||
downloadClipsBookmarksCbox.Checked = config.DownloadClipsBookmarks;
|
||||
clipsBookmarksFormatCb.SelectedItem = config.ClipsBookmarksFileFormat;
|
||||
retainAaxFileCbox.Checked = config.RetainAaxFile;
|
||||
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
|
||||
mergeOpeningEndCreditsCbox.Checked = config.MergeOpeningAndEndCredits;
|
||||
@ -44,6 +54,7 @@ namespace LibationWinForms.Dialogs
|
||||
convertFormatRb_CheckedChanged(this, EventArgs.Empty);
|
||||
allowLibationFixupCbox_CheckedChanged(this, EventArgs.Empty);
|
||||
splitFilesByChapterCbox_CheckedChanged(this, EventArgs.Empty);
|
||||
downloadClipsBookmarksCbox_CheckedChanged(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void Save_AudioSettings(Configuration config)
|
||||
@ -51,6 +62,8 @@ namespace LibationWinForms.Dialogs
|
||||
config.AllowLibationFixup = allowLibationFixupCbox.Checked;
|
||||
config.CreateCueSheet = createCueSheetCbox.Checked;
|
||||
config.DownloadCoverArt = downloadCoverArtCbox.Checked;
|
||||
config.DownloadClipsBookmarks = downloadClipsBookmarksCbox.Checked;
|
||||
config.ClipsBookmarksFileFormat = (Configuration.ClipBookmarkFormat)clipsBookmarksFormatCb.SelectedItem;
|
||||
config.RetainAaxFile = retainAaxFileCbox.Checked;
|
||||
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
|
||||
config.MergeOpeningAndEndCredits = mergeOpeningEndCreditsCbox.Checked;
|
||||
@ -68,6 +81,12 @@ namespace LibationWinForms.Dialogs
|
||||
config.ChapterTitleTemplate = chapterTitleTemplateTb.Text;
|
||||
}
|
||||
|
||||
|
||||
private void downloadClipsBookmarksCbox_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
clipsBookmarksFormatCb.Enabled = downloadClipsBookmarksCbox.Checked;
|
||||
}
|
||||
|
||||
private void lameTargetRb_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
lameBitrateGb.Enabled = lameTargetBitrateRb.Checked;
|
||||
|
||||
@ -73,6 +73,8 @@
|
||||
this.folderTemplateTb = new System.Windows.Forms.TextBox();
|
||||
this.folderTemplateLbl = new System.Windows.Forms.Label();
|
||||
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.stripUnabridgedCbox = new System.Windows.Forms.CheckBox();
|
||||
this.chapterTitleTemplateGb = new System.Windows.Forms.GroupBox();
|
||||
@ -281,7 +283,7 @@
|
||||
this.allowLibationFixupCbox.AutoSize = true;
|
||||
this.allowLibationFixupCbox.Checked = true;
|
||||
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.Size = new System.Drawing.Size(163, 19);
|
||||
this.allowLibationFixupCbox.TabIndex = 10;
|
||||
@ -633,6 +635,8 @@
|
||||
//
|
||||
// tab4AudioFileOptions
|
||||
//
|
||||
this.tab4AudioFileOptions.Controls.Add(this.clipsBookmarksFormatCb);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.downloadClipsBookmarksCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.audiobookFixupsGb);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.chapterTitleTemplateGb);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.lameOptionsGb);
|
||||
@ -649,6 +653,26 @@
|
||||
this.tab4AudioFileOptions.Text = "Audio File Options";
|
||||
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
|
||||
//
|
||||
this.audiobookFixupsGb.Controls.Add(this.splitFilesByChapterCbox);
|
||||
@ -656,7 +680,7 @@
|
||||
this.audiobookFixupsGb.Controls.Add(this.convertLosslessRb);
|
||||
this.audiobookFixupsGb.Controls.Add(this.convertLossyRb);
|
||||
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.Size = new System.Drawing.Size(403, 160);
|
||||
this.audiobookFixupsGb.TabIndex = 19;
|
||||
@ -1032,7 +1056,7 @@
|
||||
// mergeOpeningEndCreditsCbox
|
||||
//
|
||||
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.Size = new System.Drawing.Size(198, 19);
|
||||
this.mergeOpeningEndCreditsCbox.TabIndex = 13;
|
||||
@ -1042,7 +1066,7 @@
|
||||
// retainAaxFileCbox
|
||||
//
|
||||
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.Size = new System.Drawing.Size(132, 19);
|
||||
this.retainAaxFileCbox.TabIndex = 10;
|
||||
@ -1214,5 +1238,7 @@
|
||||
private System.Windows.Forms.GroupBox audiobookFixupsGb;
|
||||
private System.Windows.Forms.CheckBox betaOptInCbox;
|
||||
private System.Windows.Forms.CheckBox useCoverAsFolderIconCb;
|
||||
private System.Windows.Forms.ComboBox clipsBookmarksFormatCb;
|
||||
private System.Windows.Forms.CheckBox downloadClipsBookmarksCbox;
|
||||
}
|
||||
}
|
||||
@ -78,7 +78,7 @@ namespace LibationWinForms
|
||||
private void ToggleQueueHideBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
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)
|
||||
|
||||
@ -56,9 +56,17 @@ namespace LibationWinForms
|
||||
AccountsSettingsPersister.Saving += accountsPreSave;
|
||||
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
|
||||
Configuration.Instance.AutoScanChanged += updateAutoScanLibraryToolStripMenuItem;
|
||||
Configuration.Instance.AutoScanChanged += startAutoScan;
|
||||
updateAutoScanLibraryToolStripMenuItem(sender, e);
|
||||
startAutoScan(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
||||
|
||||
@ -87,7 +87,7 @@ namespace LibationWinForms
|
||||
|
||||
saveState.IsMaximized = form.WindowState == FormWindowState.Maximized;
|
||||
|
||||
config.SetObject(form.Name, saveState);
|
||||
config.SetNonString(saveState, form.Name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -71,6 +71,15 @@ namespace LibationWinForms.GridView
|
||||
&& updateReviewTask?.IsCompleted is not false)
|
||||
{
|
||||
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
|
||||
|
||||
private Task updateReviewTask;
|
||||
private async Task UpdateRating(Rating rating)
|
||||
private Task<bool> updateReviewTask;
|
||||
private async Task<bool> UpdateRating(Rating rating)
|
||||
{
|
||||
var api = await LibraryBook.GetApiAsync();
|
||||
|
||||
if (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));
|
||||
return await api.ReviewAsync(Book.AudibleProductId, (int)rating.OverallRating, (int)rating.PerformanceRating, (int)rating.StoryRating);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user