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;
}
//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");

View File

@ -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");

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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,7 +28,9 @@ namespace FileLiberator
public bool RetainEncryptedFile { get; init; }
public bool StripUnabridged { get; init; }
public bool CreateCueSheet { get; init; }
public ChapterInfo ChapterInfo { 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; }
public bool Downsample { get; init; }
@ -32,15 +43,56 @@ namespace FileLiberator
public string GetMultipartTitleName(MultiConvertFileProperties props)
=> Templates.ChapterTitle.GetTitle(LibraryBookDto, props);
public DownloadOptions(LibraryBook libraryBook, string downloadUrl, string userAgent)
public async Task<string> SaveClipsAndBookmarks(string fileName)
{
LibraryBookDto = ArgumentValidator
.EnsureNotNull(libraryBook, nameof(libraryBook))
.ToDto();
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)
{
LibraryBook = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook));
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
UserAgent = ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent));
// no null/empty check for key/iv. unencrypted files do not have them
}
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
}
}
}

View File

@ -46,16 +46,27 @@ namespace FileManager
return stringCache[propertyName];
}
public T GetNonString<T>(string propertyName)
{
var obj = GetObject(propertyName);
if (obj is null) return default;
if (obj is JValue jValue) return jValue.Value<T>();
if (obj is JObject jObject) return jObject.ToObject<T>();
return (T)obj;
}
public T GetNonString<T>(string propertyName)
{
var obj = GetObject(propertyName);
if (obj is null) return default;
public object GetObject(string propertyName)
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;
}
public object GetObject(string propertyName)
{
if (!objectCache.ContainsKey(propertyName))
{

View File

@ -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)
{

View File

@ -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));
}
}
}

View File

@ -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;

View File

@ -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)

View File

@ -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,7 +46,9 @@ namespace LibationFileManager
configuration.Reload();
Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(nameof(LogLevel), value));
Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new
{
LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),
LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(),

View File

@ -3,30 +3,51 @@ 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
{
public partial class Configuration
{
{
// note: any potential file manager static ctors can't compensate if storage dir is changed at run time via settings. this is partly bad architecture. but the side effect is desirable. if changing LibationFiles location: restart app
// default setting and directory creation occur in class responsible for files.
// 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;
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
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)
{
var settingWasChanged = persistentDictionary.SetWithJsonPath(jsonPath, propertyName, newValue, suppressLogging);
var settingWasChanged = persistentDictionary.SetWithJsonPath(jsonPath, propertyName, newValue, suppressLogging);
if (settingWasChanged)
configuration?.Reload();
configuration?.Reload();
}
public string SettingsFilePath => Path.Combine(LibationFiles, "Settings.json");
@ -45,161 +66,91 @@ 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);
}
[Description("Create a cue sheet (.cue)")]
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);
}
[Description("Retain the Aax file after successfully decrypting")]
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);
}
[Description("Split my books into multiple files by chapter")]
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);
}
[Description("Merge Opening/End Credits into the following/preceding chapters")]
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);
}
[Description("Strip \"(Unabridged)\" from audiobook metadata tags")]
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);
}
[Description("Strip audible branding from the start and end of audiobooks.\r\n(e.g. \"This is Audible\")")]
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);
}
[Description("Decrypt to lossy format?")]
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);
}
[Description("Lame encoder target. true = Bitrate, false = Quality")]
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);
}
[Description("Lame encoder downsamples to mono")]
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);
}
[Description("Match the source bitrate?")]
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);
}
[Description("Lame target VBR quality [10,100]")]
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);
}
[Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
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
{
get => persistentDictionary.GetNonString<bool>(nameof(DownloadCoverArt));
set => persistentDictionary.SetNonString(nameof(DownloadCoverArt), value);
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
{
[Description("Comma-separated values")]
CSV,
[Description("Microsoft Excel Spreadsheet")]
Xlsx,
[Description("JavaScript Object Notation (JSON)")]
Json
}
public enum BadBookAction
[JsonConverter(typeof(StringEnumConverter))]
public enum BadBookAction
{
[Description("Ask each time what action to take.")]
Ask = 0,
@ -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;
[Description("Download episodes? (eg: podcasts). When unchecked, episodes already in Libation will not be downloaded.")]
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);
}
[Description("Save all podcast episodes in a series to the series parent folder?")]
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
}

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>
</ItemGroup>
<ItemGroup>
<Folder Include="OSInterop\" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>

View File

@ -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);

View File

@ -80,9 +80,16 @@ namespace LibationWinForms.Dialogs
if (control is Control c)
{
if (c.InvokeRequired)
c.Invoke(new MethodInvoker(() => c.Enabled = enabled));
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 =

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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)

View File

@ -56,12 +56,20 @@ namespace LibationWinForms
AccountsSettingsPersister.Saving += accountsPreSave;
AccountsSettingsPersister.Saved += accountsPostSave;
// when autoscan setting is changed, update menu checkbox and run autoscan
Configuration.Instance.AutoScanChanged += updateAutoScanLibraryToolStripMenuItem;
Configuration.Instance.AutoScanChanged += startAutoScan;
Configuration.Instance.PropertyChanged += Configuration_PropertyChanged;
}
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
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
updateAutoScanLibraryToolStripMenuItem(sender, e);
startAutoScan(sender, e);
}
}
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
private List<(string AccountId, string LocaleName)> getDefaultAccounts()
{
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();

View File

@ -87,7 +87,7 @@ namespace LibationWinForms
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 = 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