Add Configurations property change notifications
This commit is contained in:
parent
f09baa1318
commit
1f7000c2c9
@ -43,7 +43,21 @@ namespace AaxDecrypter
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Step 3
|
//Step 3
|
||||||
|
if (DownloadOptions.DownloadClipsBookmarks)
|
||||||
|
{
|
||||||
|
Serilog.Log.Information("Begin Downloading Clips and Bookmarks");
|
||||||
|
if (await Task.Run(Step_DownloadClipsBookmarks))
|
||||||
|
Serilog.Log.Information("Completed Downloading Clips and Bookmarks");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serilog.Log.Information("Failed to Download Clips and Bookmarks");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Step 4
|
||||||
Serilog.Log.Information("Begin Cleanup");
|
Serilog.Log.Information("Begin Cleanup");
|
||||||
if (await Task.Run(Step_Cleanup))
|
if (await Task.Run(Step_Cleanup))
|
||||||
Serilog.Log.Information("Completed Cleanup");
|
Serilog.Log.Information("Completed Cleanup");
|
||||||
|
|||||||
@ -49,6 +49,19 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Step 4
|
//Step 4
|
||||||
|
if (DownloadOptions.DownloadClipsBookmarks)
|
||||||
|
{
|
||||||
|
Serilog.Log.Information("Begin Downloading Clips and Bookmarks");
|
||||||
|
if (await Task.Run(Step_DownloadClipsBookmarks))
|
||||||
|
Serilog.Log.Information("Completed Downloading Clips and Bookmarks");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serilog.Log.Information("Failed to Download Clips and Bookmarks");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Step 5
|
||||||
Serilog.Log.Information("Begin Step 4: Cleanup");
|
Serilog.Log.Information("Begin Step 4: Cleanup");
|
||||||
if (await Task.Run(Step_Cleanup))
|
if (await Task.Run(Step_Cleanup))
|
||||||
Serilog.Log.Information("Completed Step 4: Cleanup");
|
Serilog.Log.Information("Completed Step 4: Cleanup");
|
||||||
|
|||||||
@ -48,11 +48,18 @@ namespace AaxDecrypter
|
|||||||
TempFilePath = Path.ChangeExtension(jsonDownloadState, ".aaxc");
|
TempFilePath = Path.ChangeExtension(jsonDownloadState, ".aaxc");
|
||||||
|
|
||||||
DownloadOptions = ArgumentValidator.EnsureNotNull(dlOptions, nameof(dlOptions));
|
DownloadOptions = ArgumentValidator.EnsureNotNull(dlOptions, nameof(dlOptions));
|
||||||
|
DownloadOptions.PropertyChanged += DownloadOptions_PropertyChanged;
|
||||||
|
|
||||||
// delete file after validation is complete
|
// delete file after validation is complete
|
||||||
FileUtility.SaferDelete(OutputFileName);
|
FileUtility.SaferDelete(OutputFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DownloadOptions_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(DownloadOptions.DownloadSpeedBps))
|
||||||
|
InputFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
|
||||||
|
}
|
||||||
|
|
||||||
public abstract Task CancelAsync();
|
public abstract Task CancelAsync();
|
||||||
|
|
||||||
public virtual void SetCoverArt(byte[] coverArt)
|
public virtual void SetCoverArt(byte[] coverArt)
|
||||||
@ -132,14 +139,27 @@ namespace AaxDecrypter
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async Task<bool> Step_DownloadClipsBookmarks()
|
||||||
|
{
|
||||||
|
if (!IsCanceled && DownloadOptions.DownloadClipsBookmarks)
|
||||||
|
{
|
||||||
|
var recordsFile = await DownloadOptions.SaveClipsAndBookmarks(OutputFileName);
|
||||||
|
|
||||||
|
if (File.Exists(recordsFile))
|
||||||
|
OnFileCreated(recordsFile);
|
||||||
|
}
|
||||||
|
return !IsCanceled;
|
||||||
|
}
|
||||||
|
|
||||||
private NetworkFileStreamPersister OpenNetworkFileStream()
|
private NetworkFileStreamPersister OpenNetworkFileStream()
|
||||||
{
|
{
|
||||||
if (!File.Exists(jsonDownloadState))
|
NetworkFileStreamPersister nfsp = default;
|
||||||
return NewNetworkFilePersister();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var nfsp = new NetworkFileStreamPersister(jsonDownloadState);
|
if (!File.Exists(jsonDownloadState))
|
||||||
|
return nfsp = NewNetworkFilePersister();
|
||||||
|
|
||||||
|
nfsp = new NetworkFileStreamPersister(jsonDownloadState);
|
||||||
// If More than ~1 hour has elapsed since getting the download url, it will expire.
|
// If More than ~1 hour has elapsed since getting the download url, it will expire.
|
||||||
// The new url will be to the same file.
|
// The new url will be to the same file.
|
||||||
nfsp.NetworkFileStream.SetUriForSameFile(new Uri(DownloadOptions.DownloadUrl));
|
nfsp.NetworkFileStream.SetUriForSameFile(new Uri(DownloadOptions.DownloadUrl));
|
||||||
@ -149,7 +169,12 @@ namespace AaxDecrypter
|
|||||||
{
|
{
|
||||||
FileUtility.SaferDelete(jsonDownloadState);
|
FileUtility.SaferDelete(jsonDownloadState);
|
||||||
FileUtility.SaferDelete(TempFilePath);
|
FileUtility.SaferDelete(TempFilePath);
|
||||||
return NewNetworkFilePersister();
|
return nfsp = NewNetworkFilePersister();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (nfsp is not null)
|
||||||
|
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
using AAXClean;
|
using AAXClean;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace AaxDecrypter
|
namespace AaxDecrypter
|
||||||
{
|
{
|
||||||
public interface IDownloadOptions
|
public interface IDownloadOptions : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
FileManager.ReplacementCharacters ReplacementCharacters { get; }
|
FileManager.ReplacementCharacters ReplacementCharacters { get; }
|
||||||
string DownloadUrl { get; }
|
string DownloadUrl { get; }
|
||||||
@ -14,6 +16,8 @@ namespace AaxDecrypter
|
|||||||
bool RetainEncryptedFile { get; }
|
bool RetainEncryptedFile { get; }
|
||||||
bool StripUnabridged { get; }
|
bool StripUnabridged { get; }
|
||||||
bool CreateCueSheet { get; }
|
bool CreateCueSheet { get; }
|
||||||
|
bool DownloadClipsBookmarks { get; }
|
||||||
|
long DownloadSpeedBps { get; }
|
||||||
ChapterInfo ChapterInfo { get; }
|
ChapterInfo ChapterInfo { get; }
|
||||||
bool FixupFile { get; }
|
bool FixupFile { get; }
|
||||||
NAudio.Lame.LameConfig LameConfig { get; }
|
NAudio.Lame.LameConfig LameConfig { get; }
|
||||||
@ -21,5 +25,6 @@ namespace AaxDecrypter
|
|||||||
bool MatchSourceBitrate { get; }
|
bool MatchSourceBitrate { get; }
|
||||||
string GetMultipartFileName(MultiConvertFileProperties props);
|
string GetMultipartFileName(MultiConvertFileProperties props);
|
||||||
string GetMultipartTitleName(MultiConvertFileProperties props);
|
string GetMultipartTitleName(MultiConvertFileProperties props);
|
||||||
|
Task<string> SaveClipsAndBookmarks(string fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,9 +41,9 @@ namespace AaxDecrypter
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool IsCancelled => _cancellationSource.IsCancellationRequested;
|
public bool IsCancelled => _cancellationSource.IsCancellationRequested;
|
||||||
|
|
||||||
private static long _globalSpeedLimit = 0;
|
private long _speedLimit = 0;
|
||||||
/// <summary>bytes per second</summary>
|
/// <summary>bytes per second</summary>
|
||||||
public static long GlobalSpeedLimit { get => _globalSpeedLimit; set => _globalSpeedLimit = value <= 0 ? 0 : Math.Max(value, MIN_BYTES_PER_SECOND); }
|
public long SpeedLimit { get => _speedLimit; set => _speedLimit = value <= 0 ? 0 : Math.Max(value, MIN_BYTES_PER_SECOND); }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
//Minimum throttle rate. The minimum amount of data that can be throttled
|
//Minimum throttle rate. The minimum amount of data that can be throttled
|
||||||
//on each iteration of the download loop is DOWNLOAD_BUFF_SZ.
|
//on each iteration of the download loop is DOWNLOAD_BUFF_SZ.
|
||||||
private const int MIN_BYTES_PER_SECOND = DOWNLOAD_BUFF_SZ * THROTTLE_FREQUENCY;
|
public const int MIN_BYTES_PER_SECOND = DOWNLOAD_BUFF_SZ * THROTTLE_FREQUENCY;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
bytesReadSinceThrottle += bytesRead;
|
bytesReadSinceThrottle += bytesRead;
|
||||||
|
|
||||||
if (GlobalSpeedLimit >= MIN_BYTES_PER_SECOND && bytesReadSinceThrottle > GlobalSpeedLimit / THROTTLE_FREQUENCY)
|
if (SpeedLimit >= MIN_BYTES_PER_SECOND && bytesReadSinceThrottle > SpeedLimit / THROTTLE_FREQUENCY)
|
||||||
{
|
{
|
||||||
var delayMS = (int)(startTime.AddSeconds(1d / THROTTLE_FREQUENCY) - DateTime.Now).TotalMilliseconds;
|
var delayMS = (int)(startTime.AddSeconds(1d / THROTTLE_FREQUENCY) - DateTime.Now).TotalMilliseconds;
|
||||||
if (delayMS > 0)
|
if (delayMS > 0)
|
||||||
|
|||||||
@ -16,7 +16,7 @@ namespace AaxDecrypter
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Serilog.Log.Information("Begin download and convert Aaxc To {format}", DownloadOptions.OutputFormat);
|
Serilog.Log.Information("Begin downloading unencrypted audiobook.");
|
||||||
|
|
||||||
//Step 1
|
//Step 1
|
||||||
Serilog.Log.Information("Begin Step 1: Get Mp3 Metadata");
|
Serilog.Log.Information("Begin Step 1: Get Mp3 Metadata");
|
||||||
@ -39,6 +39,19 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Step 3
|
//Step 3
|
||||||
|
if (DownloadOptions.DownloadClipsBookmarks)
|
||||||
|
{
|
||||||
|
Serilog.Log.Information("Begin Downloading Clips and Bookmarks");
|
||||||
|
if (await Task.Run(Step_DownloadClipsBookmarks))
|
||||||
|
Serilog.Log.Information("Completed Downloading Clips and Bookmarks");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serilog.Log.Information("Failed to Download Clips and Bookmarks");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Step 4
|
||||||
Serilog.Log.Information("Begin Step 3: Cleanup");
|
Serilog.Log.Information("Begin Step 3: Cleanup");
|
||||||
if (await Task.Run(Step_Cleanup))
|
if (await Task.Run(Step_Cleanup))
|
||||||
Serilog.Log.Information("Completed Step 3: Cleanup");
|
Serilog.Log.Information("Completed Step 3: Cleanup");
|
||||||
@ -58,7 +71,6 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override Task CancelAsync()
|
public override Task CancelAsync()
|
||||||
{
|
{
|
||||||
IsCanceled = true;
|
IsCanceled = true;
|
||||||
|
|||||||
@ -174,6 +174,12 @@ namespace AppScaffolding
|
|||||||
if (!config.Exists(nameof(config.DownloadCoverArt)))
|
if (!config.Exists(nameof(config.DownloadCoverArt)))
|
||||||
config.DownloadCoverArt = true;
|
config.DownloadCoverArt = true;
|
||||||
|
|
||||||
|
if (!config.Exists(nameof(config.DownloadClipsBookmarks)))
|
||||||
|
config.DownloadClipsBookmarks = false;
|
||||||
|
|
||||||
|
if (!config.Exists(nameof(config.ClipsBookmarksFileFormat)))
|
||||||
|
config.ClipsBookmarksFileFormat = Configuration.ClipBookmarkFormat.CSV;
|
||||||
|
|
||||||
if (!config.Exists(nameof(config.AutoDownloadEpisodes)))
|
if (!config.Exists(nameof(config.AutoDownloadEpisodes)))
|
||||||
config.AutoDownloadEpisodes = false;
|
config.AutoDownloadEpisodes = false;
|
||||||
|
|
||||||
@ -229,7 +235,7 @@ namespace AppScaffolding
|
|||||||
{ "Using", new JArray{ "Dinah.Core", "Serilog.Exceptions" } }, // dll's name, NOT namespace
|
{ "Using", new JArray{ "Dinah.Core", "Serilog.Exceptions" } }, // dll's name, NOT namespace
|
||||||
{ "Enrich", new JArray{ "WithCaller", "WithExceptionDetails" } },
|
{ "Enrich", new JArray{ "WithCaller", "WithExceptionDetails" } },
|
||||||
};
|
};
|
||||||
config.SetObject("Serilog", serilogObj);
|
config.SetNonString(serilogObj, "Serilog");
|
||||||
}
|
}
|
||||||
|
|
||||||
// to restore original: Console.SetOut(origOut);
|
// to restore original: Console.SetOut(origOut);
|
||||||
|
|||||||
@ -104,7 +104,7 @@ namespace FileLiberator
|
|||||||
|
|
||||||
var api = await libraryBook.GetApiAsync();
|
var api = await libraryBook.GetApiAsync();
|
||||||
var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId);
|
var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId);
|
||||||
var dlOptions = BuildDownloadOptions(libraryBook, config, contentLic);
|
using var dlOptions = BuildDownloadOptions(libraryBook, config, contentLic);
|
||||||
|
|
||||||
var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, dlOptions.OutputFormat.ToString().ToLower());
|
var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, dlOptions.OutputFormat.ToString().ToLower());
|
||||||
var cacheDir = AudibleFileStorage.DownloadsInProgressDirectory;
|
var cacheDir = AudibleFileStorage.DownloadsInProgressDirectory;
|
||||||
@ -133,9 +133,7 @@ namespace FileLiberator
|
|||||||
abDownloader.FileCreated += (_, path) => OnFileCreated(libraryBook, path);
|
abDownloader.FileCreated += (_, path) => OnFileCreated(libraryBook, path);
|
||||||
|
|
||||||
// REAL WORK DONE HERE
|
// REAL WORK DONE HERE
|
||||||
var success = await abDownloader.RunAsync();
|
return await abDownloader.RunAsync();
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DownloadOptions BuildDownloadOptions(LibraryBook libraryBook, Configuration config, AudibleApi.Common.ContentLicense contentLic)
|
private DownloadOptions BuildDownloadOptions(LibraryBook libraryBook, Configuration config, AudibleApi.Common.ContentLicense contentLic)
|
||||||
@ -168,6 +166,8 @@ namespace FileLiberator
|
|||||||
Downsample = config.AllowLibationFixup && config.LameDownsampleMono,
|
Downsample = config.AllowLibationFixup && config.LameDownsampleMono,
|
||||||
MatchSourceBitrate = config.AllowLibationFixup && config.LameMatchSourceBR && config.LameTargetBitrate,
|
MatchSourceBitrate = config.AllowLibationFixup && config.LameMatchSourceBR && config.LameTargetBitrate,
|
||||||
CreateCueSheet = config.CreateCueSheet,
|
CreateCueSheet = config.CreateCueSheet,
|
||||||
|
DownloadClipsBookmarks = config.DownloadClipsBookmarks,
|
||||||
|
DownloadSpeedBps = config.DownloadSpeedLimit,
|
||||||
LameConfig = GetLameOptions(config),
|
LameConfig = GetLameOptions(config),
|
||||||
ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(chapterStartMs)),
|
ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(chapterStartMs)),
|
||||||
FixupFile = config.AllowLibationFixup
|
FixupFile = config.AllowLibationFixup
|
||||||
|
|||||||
@ -4,11 +4,20 @@ using Dinah.Core;
|
|||||||
using DataLayer;
|
using DataLayer;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using ApplicationServices;
|
||||||
|
|
||||||
namespace FileLiberator
|
namespace FileLiberator
|
||||||
{
|
{
|
||||||
public class DownloadOptions : IDownloadOptions
|
public class DownloadOptions : IDownloadOptions, IDisposable
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
private readonly IDisposable cancellation;
|
||||||
|
public LibraryBook LibraryBook { get; }
|
||||||
public LibraryBookDto LibraryBookDto { get; }
|
public LibraryBookDto LibraryBookDto { get; }
|
||||||
public string DownloadUrl { get; }
|
public string DownloadUrl { get; }
|
||||||
public string UserAgent { get; }
|
public string UserAgent { get; }
|
||||||
@ -19,6 +28,8 @@ namespace FileLiberator
|
|||||||
public bool RetainEncryptedFile { get; init; }
|
public bool RetainEncryptedFile { get; init; }
|
||||||
public bool StripUnabridged { get; init; }
|
public bool StripUnabridged { get; init; }
|
||||||
public bool CreateCueSheet { get; init; }
|
public bool CreateCueSheet { get; init; }
|
||||||
|
public bool DownloadClipsBookmarks { get; init; }
|
||||||
|
public long DownloadSpeedBps { get; set; }
|
||||||
public ChapterInfo ChapterInfo { get; init; }
|
public ChapterInfo ChapterInfo { get; init; }
|
||||||
public bool FixupFile { get; init; }
|
public bool FixupFile { get; init; }
|
||||||
public NAudio.Lame.LameConfig LameConfig { get; init; }
|
public NAudio.Lame.LameConfig LameConfig { get; init; }
|
||||||
@ -32,14 +43,55 @@ namespace FileLiberator
|
|||||||
public string GetMultipartTitleName(MultiConvertFileProperties props)
|
public string GetMultipartTitleName(MultiConvertFileProperties props)
|
||||||
=> Templates.ChapterTitle.GetTitle(LibraryBookDto, props);
|
=> Templates.ChapterTitle.GetTitle(LibraryBookDto, props);
|
||||||
|
|
||||||
|
public async Task<string> SaveClipsAndBookmarks(string fileName)
|
||||||
|
{
|
||||||
|
if (DownloadClipsBookmarks)
|
||||||
|
{
|
||||||
|
var format = Configuration.Instance.ClipsBookmarksFileFormat;
|
||||||
|
|
||||||
|
var formatExtension = format.ToString().ToLowerInvariant();
|
||||||
|
var filePath = Path.ChangeExtension(fileName, formatExtension);
|
||||||
|
|
||||||
|
var api = await LibraryBook.GetApiAsync();
|
||||||
|
var records = await api.GetRecordsAsync(LibraryBook.Book.AudibleProductId);
|
||||||
|
|
||||||
|
switch(format)
|
||||||
|
{
|
||||||
|
case Configuration.ClipBookmarkFormat.CSV:
|
||||||
|
RecordExporter.ToCsv(filePath, records);
|
||||||
|
break;
|
||||||
|
case Configuration.ClipBookmarkFormat.Xlsx:
|
||||||
|
RecordExporter.ToXlsx(filePath, records);
|
||||||
|
break;
|
||||||
|
case Configuration.ClipBookmarkFormat.Json:
|
||||||
|
RecordExporter.ToJson(filePath, LibraryBook, records);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DownloadSpeedChanged(string propertyName, long speed)
|
||||||
|
{
|
||||||
|
DownloadSpeedBps = speed;
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DownloadSpeedBps)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
cancellation?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public DownloadOptions(LibraryBook libraryBook, string downloadUrl, string userAgent)
|
public DownloadOptions(LibraryBook libraryBook, string downloadUrl, string userAgent)
|
||||||
{
|
{
|
||||||
LibraryBookDto = ArgumentValidator
|
LibraryBook = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook));
|
||||||
.EnsureNotNull(libraryBook, nameof(libraryBook))
|
|
||||||
.ToDto();
|
|
||||||
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
|
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
|
||||||
UserAgent = ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent));
|
UserAgent = ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent));
|
||||||
|
|
||||||
|
LibraryBookDto = LibraryBook.ToDto();
|
||||||
|
|
||||||
|
cancellation = Configuration.Instance.SubscribeToPropertyChanged<long>(nameof(Configuration.DownloadSpeedLimit), DownloadSpeedChanged);
|
||||||
// no null/empty check for key/iv. unencrypted files do not have them
|
// no null/empty check for key/iv. unencrypted files do not have them
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,18 @@ namespace FileManager
|
|||||||
{
|
{
|
||||||
var obj = GetObject(propertyName);
|
var obj = GetObject(propertyName);
|
||||||
if (obj is null) return default;
|
if (obj is null) return default;
|
||||||
if (obj is JValue jValue) return jValue.Value<T>();
|
|
||||||
|
if (obj is JValue jValue)
|
||||||
|
{
|
||||||
|
if (jValue.Type == JTokenType.String && typeof(T).IsAssignableTo(typeof(Enum)))
|
||||||
|
{
|
||||||
|
return
|
||||||
|
Enum.TryParse(typeof(T), jValue.Value<string>(), out var enumVal)
|
||||||
|
? (T)enumVal
|
||||||
|
: Enum.GetValues(typeof(T)).Cast<T>().First();
|
||||||
|
}
|
||||||
|
return jValue.Value<T>();
|
||||||
|
}
|
||||||
if (obj is JObject jObject) return jObject.ToObject<T>();
|
if (obj is JObject jObject) return jObject.ToObject<T>();
|
||||||
return (T)obj;
|
return (T)obj;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,7 +93,7 @@ namespace LibationAvalonia
|
|||||||
saveState.Width = (int)form.Bounds.Size.Width;
|
saveState.Width = (int)form.Bounds.Size.Width;
|
||||||
saveState.Height = (int)form.Bounds.Size.Height;
|
saveState.Height = (int)form.Bounds.Size.Height;
|
||||||
|
|
||||||
config.SetObject(form.GetType().Name, saveState);
|
config.SetNonString(saveState, form.GetType().Name);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -56,7 +56,7 @@ namespace LibationAvalonia.Views
|
|||||||
public void ToggleQueueHideBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public void ToggleQueueHideBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
SetQueueCollapseState(_viewModel.QueueOpen);
|
SetQueueCollapseState(_viewModel.QueueOpen);
|
||||||
Configuration.Instance.SetObject(nameof(_viewModel.QueueOpen), _viewModel.QueueOpen);
|
Configuration.Instance.SetNonString(_viewModel.QueueOpen, nameof(_viewModel.QueueOpen));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using Dinah.Core;
|
|||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace LibationAvalonia.Views
|
namespace LibationAvalonia.Views
|
||||||
@ -13,6 +14,7 @@ namespace LibationAvalonia.Views
|
|||||||
public partial class MainWindow
|
public partial class MainWindow
|
||||||
{
|
{
|
||||||
private InterruptableTimer autoScanTimer;
|
private InterruptableTimer autoScanTimer;
|
||||||
|
private IDisposable cancellation;
|
||||||
|
|
||||||
private void Configure_ScanAuto()
|
private void Configure_ScanAuto()
|
||||||
{
|
{
|
||||||
@ -53,7 +55,11 @@ namespace LibationAvalonia.Views
|
|||||||
AccountsSettingsPersister.Saved += accountsPostSave;
|
AccountsSettingsPersister.Saved += accountsPostSave;
|
||||||
|
|
||||||
// when autoscan setting is changed, update menu checkbox and run autoscan
|
// when autoscan setting is changed, update menu checkbox and run autoscan
|
||||||
Configuration.Instance.AutoScanChanged += startAutoScan;
|
Configuration.Instance.PropertyChanged += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(Configuration.Instance.AutoScan))
|
||||||
|
startAutoScan();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
||||||
|
|||||||
@ -97,13 +97,13 @@ namespace LibationAvalonia.Views
|
|||||||
const string ignoreUpdate = "IgnoreUpdate";
|
const string ignoreUpdate = "IgnoreUpdate";
|
||||||
var config = Configuration.Instance;
|
var config = Configuration.Instance;
|
||||||
|
|
||||||
if (config.GetObject(ignoreUpdate)?.ToString() == upgradeProperties.LatestRelease.ToString())
|
if (config.GetString(ignoreUpdate) == upgradeProperties.LatestRelease.ToString())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var notificationResult = await new UpgradeNotification(upgradeProperties).ShowDialog<DialogResult>(this);
|
var notificationResult = await new UpgradeNotification(upgradeProperties).ShowDialog<DialogResult>(this);
|
||||||
|
|
||||||
if (notificationResult == DialogResult.Ignore)
|
if (notificationResult == DialogResult.Ignore)
|
||||||
config.SetObject(ignoreUpdate, upgradeProperties.LatestRelease.ToString());
|
config.SetString(upgradeProperties.LatestRelease.ToString(), ignoreUpdate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@ -36,6 +36,7 @@ namespace LibationFileManager
|
|||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(nameof(LogLevel), LogLevel, value));
|
||||||
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
|
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
|
||||||
if (!valueWasChanged)
|
if (!valueWasChanged)
|
||||||
{
|
{
|
||||||
@ -45,6 +46,8 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
configuration.Reload();
|
configuration.Reload();
|
||||||
|
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(nameof(LogLevel), value));
|
||||||
|
|
||||||
Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new
|
Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new
|
||||||
{
|
{
|
||||||
LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),
|
LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),
|
||||||
|
|||||||
@ -3,7 +3,10 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
@ -15,11 +18,29 @@ namespace LibationFileManager
|
|||||||
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
|
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
|
||||||
// exceptions: appsettings.json, LibationFiles dir, Settings.json
|
// exceptions: appsettings.json, LibationFiles dir, Settings.json
|
||||||
|
|
||||||
private PersistentDictionary persistentDictionary;
|
private PersistentDictionary persistentDictionary { get; set; }
|
||||||
|
|
||||||
public T GetNonString<T>(string propertyName) => persistentDictionary.GetNonString<T>(propertyName);
|
public T GetNonString<T>([CallerMemberName] string propertyName = "") => persistentDictionary.GetNonString<T>(propertyName);
|
||||||
public object GetObject(string propertyName) => persistentDictionary.GetObject(propertyName);
|
public object GetObject([CallerMemberName] string propertyName = "") => persistentDictionary.GetObject(propertyName);
|
||||||
public void SetObject(string propertyName, object newValue) => persistentDictionary.SetNonString(propertyName, newValue);
|
public string GetString([CallerMemberName] string propertyName = "") => persistentDictionary.GetString(propertyName);
|
||||||
|
public void SetNonString(object newValue, [CallerMemberName] string propertyName = "")
|
||||||
|
{
|
||||||
|
var existing = GetType().GetProperty(propertyName)?.GetValue(this);
|
||||||
|
if (existing?.Equals(newValue) is true) return;
|
||||||
|
|
||||||
|
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
|
||||||
|
persistentDictionary.SetNonString(propertyName, newValue);
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, newValue));
|
||||||
|
}
|
||||||
|
public void SetString(string newValue, [CallerMemberName] string propertyName = "")
|
||||||
|
{
|
||||||
|
var existing = GetType().GetProperty(propertyName)?.GetValue(this);
|
||||||
|
if (existing?.Equals(newValue) is true) return;
|
||||||
|
|
||||||
|
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
|
||||||
|
persistentDictionary.SetString(propertyName, newValue);
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, newValue));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
||||||
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
|
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
|
||||||
@ -45,160 +66,90 @@ namespace LibationFileManager
|
|||||||
public bool Exists(string propertyName) => persistentDictionary.Exists(propertyName);
|
public bool Exists(string propertyName) => persistentDictionary.Exists(propertyName);
|
||||||
|
|
||||||
[Description("Set cover art as the folder's icon. (Windows only)")]
|
[Description("Set cover art as the folder's icon. (Windows only)")]
|
||||||
public bool UseCoverAsFolderIcon
|
public bool UseCoverAsFolderIcon { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(UseCoverAsFolderIcon));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(UseCoverAsFolderIcon), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Use the beta version of Libation\r\nNew and experimental features, but probably buggy.\r\n(requires restart to take effect)")]
|
[Description("Use the beta version of Libation\r\nNew and experimental features, but probably buggy.\r\n(requires restart to take effect)")]
|
||||||
public bool BetaOptIn
|
public bool BetaOptIn { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(BetaOptIn));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(BetaOptIn), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Location for book storage. Includes destination of newly liberated books")]
|
[Description("Location for book storage. Includes destination of newly liberated books")]
|
||||||
public string Books
|
public string Books { get => GetString(); set => SetString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetString(nameof(Books));
|
|
||||||
set => persistentDictionary.SetString(nameof(Books), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// temp/working dir(s) should be outside of dropbox
|
// temp/working dir(s) should be outside of dropbox
|
||||||
[Description("Temporary location of files while they're in process of being downloaded and decrypted.\r\nWhen decryption is complete, the final file will be in Books location\r\nRecommend not using a folder which is backed up real time. Eg: Dropbox, iCloud, Google Drive")]
|
[Description("Temporary location of files while they're in process of being downloaded and decrypted.\r\nWhen decryption is complete, the final file will be in Books location\r\nRecommend not using a folder which is backed up real time. Eg: Dropbox, iCloud, Google Drive")]
|
||||||
public string InProgress
|
public string InProgress { get => GetString(); set => SetString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetString(nameof(InProgress));
|
|
||||||
set => persistentDictionary.SetString(nameof(InProgress), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Allow Libation to fix up audiobook metadata")]
|
[Description("Allow Libation to fix up audiobook metadata")]
|
||||||
public bool AllowLibationFixup
|
public bool AllowLibationFixup { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(AllowLibationFixup));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(AllowLibationFixup), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Create a cue sheet (.cue)")]
|
[Description("Create a cue sheet (.cue)")]
|
||||||
public bool CreateCueSheet
|
public bool CreateCueSheet { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(CreateCueSheet));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(CreateCueSheet), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Retain the Aax file after successfully decrypting")]
|
[Description("Retain the Aax file after successfully decrypting")]
|
||||||
public bool RetainAaxFile
|
public bool RetainAaxFile { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(RetainAaxFile));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(RetainAaxFile), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Split my books into multiple files by chapter")]
|
[Description("Split my books into multiple files by chapter")]
|
||||||
public bool SplitFilesByChapter
|
public bool SplitFilesByChapter { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(SplitFilesByChapter));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Merge Opening/End Credits into the following/preceding chapters")]
|
[Description("Merge Opening/End Credits into the following/preceding chapters")]
|
||||||
public bool MergeOpeningAndEndCredits
|
public bool MergeOpeningAndEndCredits { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(MergeOpeningAndEndCredits));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(MergeOpeningAndEndCredits), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Strip \"(Unabridged)\" from audiobook metadata tags")]
|
[Description("Strip \"(Unabridged)\" from audiobook metadata tags")]
|
||||||
public bool StripUnabridged
|
public bool StripUnabridged { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(StripUnabridged));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(StripUnabridged), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Strip audible branding from the start and end of audiobooks.\r\n(e.g. \"This is Audible\")")]
|
[Description("Strip audible branding from the start and end of audiobooks.\r\n(e.g. \"This is Audible\")")]
|
||||||
public bool StripAudibleBrandAudio
|
public bool StripAudibleBrandAudio { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(StripAudibleBrandAudio));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(StripAudibleBrandAudio), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Decrypt to lossy format?")]
|
[Description("Decrypt to lossy format?")]
|
||||||
public bool DecryptToLossy
|
public bool DecryptToLossy { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(DecryptToLossy));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(DecryptToLossy), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Lame encoder target. true = Bitrate, false = Quality")]
|
[Description("Lame encoder target. true = Bitrate, false = Quality")]
|
||||||
public bool LameTargetBitrate
|
public bool LameTargetBitrate { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(LameTargetBitrate));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameTargetBitrate), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Lame encoder downsamples to mono")]
|
[Description("Lame encoder downsamples to mono")]
|
||||||
public bool LameDownsampleMono
|
public bool LameDownsampleMono { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(LameDownsampleMono));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameDownsampleMono), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Lame target bitrate [16,320]")]
|
[Description("Lame target bitrate [16,320]")]
|
||||||
public int LameBitrate
|
public int LameBitrate { get => GetNonString<int>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<int>(nameof(LameBitrate));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameBitrate), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Restrict encoder to constant bitrate?")]
|
[Description("Restrict encoder to constant bitrate?")]
|
||||||
public bool LameConstantBitrate
|
public bool LameConstantBitrate { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(LameConstantBitrate));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameConstantBitrate), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Match the source bitrate?")]
|
[Description("Match the source bitrate?")]
|
||||||
public bool LameMatchSourceBR
|
public bool LameMatchSourceBR { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(LameMatchSourceBR));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameMatchSourceBR), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Lame target VBR quality [10,100]")]
|
[Description("Lame target VBR quality [10,100]")]
|
||||||
public int LameVBRQuality
|
public int LameVBRQuality { get => GetNonString<int>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<int>(nameof(LameVBRQuality));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameVBRQuality), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
|
[Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
|
||||||
public Dictionary<string, bool> GridColumnsVisibilities
|
public Dictionary<string, bool> GridColumnsVisibilities { get => GetNonString<Dictionary<string, bool>>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<Dictionary<string, bool>>(nameof(GridColumnsVisibilities));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(GridColumnsVisibilities), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")]
|
[Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")]
|
||||||
public Dictionary<string, int> GridColumnsDisplayIndices
|
public Dictionary<string, int> GridColumnsDisplayIndices { get => GetNonString<Dictionary<string, int>>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<Dictionary<string, int>>(nameof(GridColumnsDisplayIndices));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(GridColumnsDisplayIndices), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")]
|
[Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")]
|
||||||
public Dictionary<string, int> GridColumnsWidths
|
public Dictionary<string, int> GridColumnsWidths { get => GetNonString<Dictionary<string, int>>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<Dictionary<string, int>>(nameof(GridColumnsWidths));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(GridColumnsWidths), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Save cover image alongside audiobook?")]
|
[Description("Save cover image alongside audiobook?")]
|
||||||
public bool DownloadCoverArt
|
public bool DownloadCoverArt { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
|
|
||||||
|
[Description("Download clips and bookmarks?")]
|
||||||
|
public bool DownloadClipsBookmarks { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
|
|
||||||
|
[Description("File format to save clips and bookmarks")]
|
||||||
|
public ClipBookmarkFormat ClipsBookmarksFileFormat { get => GetNonString<ClipBookmarkFormat>(); set => SetNonString(value); }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public enum ClipBookmarkFormat
|
||||||
{
|
{
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(DownloadCoverArt));
|
[Description("Comma-separated values")]
|
||||||
set => persistentDictionary.SetNonString(nameof(DownloadCoverArt), value);
|
CSV,
|
||||||
|
[Description("Microsoft Excel Spreadsheet")]
|
||||||
|
Xlsx,
|
||||||
|
[Description("JavaScript Object Notation (JSON)")]
|
||||||
|
Json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public enum BadBookAction
|
public enum BadBookAction
|
||||||
{
|
{
|
||||||
[Description("Ask each time what action to take.")]
|
[Description("Ask each time what action to take.")]
|
||||||
@ -212,91 +163,46 @@ namespace LibationFileManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Description("When liberating books and there is an error, Libation should:")]
|
[Description("When liberating books and there is an error, Libation should:")]
|
||||||
public BadBookAction BadBook
|
public BadBookAction BadBook { get => GetNonString<BadBookAction>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var badBookStr = persistentDictionary.GetString(nameof(BadBook));
|
|
||||||
return Enum.TryParse<BadBookAction>(badBookStr, out var badBookEnum) ? badBookEnum : BadBookAction.Ask;
|
|
||||||
}
|
|
||||||
set => persistentDictionary.SetString(nameof(BadBook), value.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Show number of newly imported titles? When unchecked, no pop-up will appear after library scan.")]
|
[Description("Show number of newly imported titles? When unchecked, no pop-up will appear after library scan.")]
|
||||||
public bool ShowImportedStats
|
public bool ShowImportedStats { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(ShowImportedStats));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(ShowImportedStats), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Import episodes? (eg: podcasts) When unchecked, episodes will not be imported into Libation.")]
|
[Description("Import episodes? (eg: podcasts) When unchecked, episodes will not be imported into Libation.")]
|
||||||
public bool ImportEpisodes
|
public bool ImportEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(ImportEpisodes));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(ImportEpisodes), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Download episodes? (eg: podcasts). When unchecked, episodes already in Libation will not be downloaded.")]
|
[Description("Download episodes? (eg: podcasts). When unchecked, episodes already in Libation will not be downloaded.")]
|
||||||
public bool DownloadEpisodes
|
public bool DownloadEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(DownloadEpisodes));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(DownloadEpisodes), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler AutoScanChanged;
|
|
||||||
|
|
||||||
[Description("Automatically run periodic scans in the background?")]
|
[Description("Automatically run periodic scans in the background?")]
|
||||||
public bool AutoScan
|
public bool AutoScan { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(AutoScan));
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (AutoScan != value)
|
|
||||||
{
|
|
||||||
persistentDictionary.SetNonString(nameof(AutoScan), value);
|
|
||||||
AutoScanChanged?.Invoke(null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Auto download books? After scan, download new books in 'checked' accounts.")]
|
[Description("Auto download books? After scan, download new books in 'checked' accounts.")]
|
||||||
// poorly named setting. Should just be 'AutoDownload'. It is NOT episode specific
|
// poorly named setting. Should just be 'AutoDownload'. It is NOT episode specific
|
||||||
public bool AutoDownloadEpisodes
|
public bool AutoDownloadEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(AutoDownloadEpisodes));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(AutoDownloadEpisodes), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Save all podcast episodes in a series to the series parent folder?")]
|
[Description("Save all podcast episodes in a series to the series parent folder?")]
|
||||||
public bool SavePodcastsToParentFolder
|
public bool SavePodcastsToParentFolder { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(SavePodcastsToParentFolder));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(SavePodcastsToParentFolder), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Global download speed limit in bytes per second.")]
|
[Description("Global download speed limit in bytes per second.")]
|
||||||
public long DownloadSpeedLimit
|
public long DownloadSpeedLimit
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
AaxDecrypter.NetworkFileStream.GlobalSpeedLimit = persistentDictionary.GetNonString<long>(nameof(DownloadSpeedLimit));
|
var limit = GetNonString<long>();
|
||||||
return AaxDecrypter.NetworkFileStream.GlobalSpeedLimit;
|
return limit <= 0 ? 0 : Math.Max(limit, AaxDecrypter.NetworkFileStream.MIN_BYTES_PER_SECOND);
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
AaxDecrypter.NetworkFileStream.GlobalSpeedLimit = value;
|
var limit = value <= 0 ? 0 : Math.Max(value, AaxDecrypter.NetworkFileStream.MIN_BYTES_PER_SECOND);
|
||||||
persistentDictionary.SetNonString(nameof(DownloadSpeedLimit), AaxDecrypter.NetworkFileStream.GlobalSpeedLimit);
|
SetNonString(limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region templates: custom file naming
|
#region templates: custom file naming
|
||||||
|
|
||||||
[Description("Edit how filename characters are replaced")]
|
[Description("Edit how filename characters are replaced")]
|
||||||
public ReplacementCharacters ReplacementCharacters
|
public ReplacementCharacters ReplacementCharacters { get => GetNonString<ReplacementCharacters>(); set => SetNonString(value); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<ReplacementCharacters>(nameof(ReplacementCharacters));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(ReplacementCharacters), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("How to format the folders in which files will be saved")]
|
[Description("How to format the folders in which files will be saved")]
|
||||||
public string FolderTemplate
|
public string FolderTemplate
|
||||||
@ -326,12 +232,12 @@ namespace LibationFileManager
|
|||||||
set => setTemplate(nameof(ChapterTitleTemplate), Templates.ChapterTitle, value);
|
set => setTemplate(nameof(ChapterTitleTemplate), Templates.ChapterTitle, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string getTemplate(string settingName, Templates templ) => templ.GetValid(persistentDictionary.GetString(settingName));
|
private string getTemplate(string settingName, Templates templ) => templ.GetValid(GetString(settingName));
|
||||||
private void setTemplate(string settingName, Templates templ, string newValue)
|
private void setTemplate(string settingName, Templates templ, string newValue)
|
||||||
{
|
{
|
||||||
var template = newValue?.Trim();
|
var template = newValue?.Trim();
|
||||||
if (templ.IsValid(template))
|
if (templ.IsValid(template))
|
||||||
persistentDictionary.SetString(settingName, template);
|
SetString(template, settingName);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
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>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="OSInterop\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@ -275,6 +275,8 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props, string fullDirPath, ReplacementCharacters replacements = null)
|
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props, string fullDirPath, ReplacementCharacters replacements = null)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(template)) return string.Empty;
|
||||||
|
|
||||||
replacements ??= Configuration.Instance.ReplacementCharacters;
|
replacements ??= Configuration.Instance.ReplacementCharacters;
|
||||||
var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, Path.GetExtension(props.OutputFileName));
|
var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, Path.GetExtension(props.OutputFileName));
|
||||||
|
|
||||||
@ -319,7 +321,8 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
public string GetPortionTitle(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props)
|
public string GetPortionTitle(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
|
if (string.IsNullOrEmpty(template)) return string.Empty;
|
||||||
|
|
||||||
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
||||||
|
|
||||||
var fileNamingTemplate = new MetadataNamingTemplate(template);
|
var fileNamingTemplate = new MetadataNamingTemplate(template);
|
||||||
|
|||||||
@ -80,9 +80,16 @@ namespace LibationWinForms.Dialogs
|
|||||||
if (control is Control c)
|
if (control is Control c)
|
||||||
{
|
{
|
||||||
if (c.InvokeRequired)
|
if (c.InvokeRequired)
|
||||||
c.Invoke(new MethodInvoker(() => c.Enabled = enabled));
|
c.Invoke(new MethodInvoker(() =>
|
||||||
else
|
{
|
||||||
c.Enabled = enabled;
|
c.Enabled = enabled;
|
||||||
|
c.Focus();
|
||||||
|
}));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
c.Enabled = enabled;
|
||||||
|
c.Focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +173,8 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
private async Task saveRecords(IEnumerable<IRecord> records)
|
private async Task saveRecords(IEnumerable<IRecord> records)
|
||||||
{
|
{
|
||||||
|
if (!records.Any()) return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var saveFileDialog =
|
var saveFileDialog =
|
||||||
|
|||||||
@ -17,9 +17,19 @@ namespace LibationWinForms.Dialogs
|
|||||||
this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio));
|
this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio));
|
||||||
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
|
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
|
||||||
|
|
||||||
|
clipsBookmarksFormatCb.Items.AddRange(
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
Configuration.ClipBookmarkFormat.CSV,
|
||||||
|
Configuration.ClipBookmarkFormat.Xlsx,
|
||||||
|
Configuration.ClipBookmarkFormat.Json
|
||||||
|
});
|
||||||
|
|
||||||
allowLibationFixupCbox.Checked = config.AllowLibationFixup;
|
allowLibationFixupCbox.Checked = config.AllowLibationFixup;
|
||||||
createCueSheetCbox.Checked = config.CreateCueSheet;
|
createCueSheetCbox.Checked = config.CreateCueSheet;
|
||||||
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
|
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
|
||||||
|
downloadClipsBookmarksCbox.Checked = config.DownloadClipsBookmarks;
|
||||||
|
clipsBookmarksFormatCb.SelectedItem = config.ClipsBookmarksFileFormat;
|
||||||
retainAaxFileCbox.Checked = config.RetainAaxFile;
|
retainAaxFileCbox.Checked = config.RetainAaxFile;
|
||||||
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
|
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
|
||||||
mergeOpeningEndCreditsCbox.Checked = config.MergeOpeningAndEndCredits;
|
mergeOpeningEndCreditsCbox.Checked = config.MergeOpeningAndEndCredits;
|
||||||
@ -44,6 +54,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
convertFormatRb_CheckedChanged(this, EventArgs.Empty);
|
convertFormatRb_CheckedChanged(this, EventArgs.Empty);
|
||||||
allowLibationFixupCbox_CheckedChanged(this, EventArgs.Empty);
|
allowLibationFixupCbox_CheckedChanged(this, EventArgs.Empty);
|
||||||
splitFilesByChapterCbox_CheckedChanged(this, EventArgs.Empty);
|
splitFilesByChapterCbox_CheckedChanged(this, EventArgs.Empty);
|
||||||
|
downloadClipsBookmarksCbox_CheckedChanged(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Save_AudioSettings(Configuration config)
|
private void Save_AudioSettings(Configuration config)
|
||||||
@ -51,6 +62,8 @@ namespace LibationWinForms.Dialogs
|
|||||||
config.AllowLibationFixup = allowLibationFixupCbox.Checked;
|
config.AllowLibationFixup = allowLibationFixupCbox.Checked;
|
||||||
config.CreateCueSheet = createCueSheetCbox.Checked;
|
config.CreateCueSheet = createCueSheetCbox.Checked;
|
||||||
config.DownloadCoverArt = downloadCoverArtCbox.Checked;
|
config.DownloadCoverArt = downloadCoverArtCbox.Checked;
|
||||||
|
config.DownloadClipsBookmarks = downloadClipsBookmarksCbox.Checked;
|
||||||
|
config.ClipsBookmarksFileFormat = (Configuration.ClipBookmarkFormat)clipsBookmarksFormatCb.SelectedItem;
|
||||||
config.RetainAaxFile = retainAaxFileCbox.Checked;
|
config.RetainAaxFile = retainAaxFileCbox.Checked;
|
||||||
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
|
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
|
||||||
config.MergeOpeningAndEndCredits = mergeOpeningEndCreditsCbox.Checked;
|
config.MergeOpeningAndEndCredits = mergeOpeningEndCreditsCbox.Checked;
|
||||||
@ -68,6 +81,12 @@ namespace LibationWinForms.Dialogs
|
|||||||
config.ChapterTitleTemplate = chapterTitleTemplateTb.Text;
|
config.ChapterTitleTemplate = chapterTitleTemplateTb.Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void downloadClipsBookmarksCbox_CheckedChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
clipsBookmarksFormatCb.Enabled = downloadClipsBookmarksCbox.Checked;
|
||||||
|
}
|
||||||
|
|
||||||
private void lameTargetRb_CheckedChanged(object sender, EventArgs e)
|
private void lameTargetRb_CheckedChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
lameBitrateGb.Enabled = lameTargetBitrateRb.Checked;
|
lameBitrateGb.Enabled = lameTargetBitrateRb.Checked;
|
||||||
|
|||||||
@ -73,6 +73,8 @@
|
|||||||
this.folderTemplateTb = new System.Windows.Forms.TextBox();
|
this.folderTemplateTb = new System.Windows.Forms.TextBox();
|
||||||
this.folderTemplateLbl = new System.Windows.Forms.Label();
|
this.folderTemplateLbl = new System.Windows.Forms.Label();
|
||||||
this.tab4AudioFileOptions = new System.Windows.Forms.TabPage();
|
this.tab4AudioFileOptions = new System.Windows.Forms.TabPage();
|
||||||
|
this.clipsBookmarksFormatCb = new System.Windows.Forms.ComboBox();
|
||||||
|
this.downloadClipsBookmarksCbox = new System.Windows.Forms.CheckBox();
|
||||||
this.audiobookFixupsGb = new System.Windows.Forms.GroupBox();
|
this.audiobookFixupsGb = new System.Windows.Forms.GroupBox();
|
||||||
this.stripUnabridgedCbox = new System.Windows.Forms.CheckBox();
|
this.stripUnabridgedCbox = new System.Windows.Forms.CheckBox();
|
||||||
this.chapterTitleTemplateGb = new System.Windows.Forms.GroupBox();
|
this.chapterTitleTemplateGb = new System.Windows.Forms.GroupBox();
|
||||||
@ -281,7 +283,7 @@
|
|||||||
this.allowLibationFixupCbox.AutoSize = true;
|
this.allowLibationFixupCbox.AutoSize = true;
|
||||||
this.allowLibationFixupCbox.Checked = true;
|
this.allowLibationFixupCbox.Checked = true;
|
||||||
this.allowLibationFixupCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
this.allowLibationFixupCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||||
this.allowLibationFixupCbox.Location = new System.Drawing.Point(19, 118);
|
this.allowLibationFixupCbox.Location = new System.Drawing.Point(19, 143);
|
||||||
this.allowLibationFixupCbox.Name = "allowLibationFixupCbox";
|
this.allowLibationFixupCbox.Name = "allowLibationFixupCbox";
|
||||||
this.allowLibationFixupCbox.Size = new System.Drawing.Size(163, 19);
|
this.allowLibationFixupCbox.Size = new System.Drawing.Size(163, 19);
|
||||||
this.allowLibationFixupCbox.TabIndex = 10;
|
this.allowLibationFixupCbox.TabIndex = 10;
|
||||||
@ -633,6 +635,8 @@
|
|||||||
//
|
//
|
||||||
// tab4AudioFileOptions
|
// tab4AudioFileOptions
|
||||||
//
|
//
|
||||||
|
this.tab4AudioFileOptions.Controls.Add(this.clipsBookmarksFormatCb);
|
||||||
|
this.tab4AudioFileOptions.Controls.Add(this.downloadClipsBookmarksCbox);
|
||||||
this.tab4AudioFileOptions.Controls.Add(this.audiobookFixupsGb);
|
this.tab4AudioFileOptions.Controls.Add(this.audiobookFixupsGb);
|
||||||
this.tab4AudioFileOptions.Controls.Add(this.chapterTitleTemplateGb);
|
this.tab4AudioFileOptions.Controls.Add(this.chapterTitleTemplateGb);
|
||||||
this.tab4AudioFileOptions.Controls.Add(this.lameOptionsGb);
|
this.tab4AudioFileOptions.Controls.Add(this.lameOptionsGb);
|
||||||
@ -649,6 +653,26 @@
|
|||||||
this.tab4AudioFileOptions.Text = "Audio File Options";
|
this.tab4AudioFileOptions.Text = "Audio File Options";
|
||||||
this.tab4AudioFileOptions.UseVisualStyleBackColor = true;
|
this.tab4AudioFileOptions.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
|
// clipsBookmarksFormatCb
|
||||||
|
//
|
||||||
|
this.clipsBookmarksFormatCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||||
|
this.clipsBookmarksFormatCb.FormattingEnabled = true;
|
||||||
|
this.clipsBookmarksFormatCb.Location = new System.Drawing.Point(269, 64);
|
||||||
|
this.clipsBookmarksFormatCb.Name = "clipsBookmarksFormatCb";
|
||||||
|
this.clipsBookmarksFormatCb.Size = new System.Drawing.Size(67, 23);
|
||||||
|
this.clipsBookmarksFormatCb.TabIndex = 21;
|
||||||
|
//
|
||||||
|
// downloadClipsBookmarksCbox
|
||||||
|
//
|
||||||
|
this.downloadClipsBookmarksCbox.AutoSize = true;
|
||||||
|
this.downloadClipsBookmarksCbox.Location = new System.Drawing.Point(19, 68);
|
||||||
|
this.downloadClipsBookmarksCbox.Name = "downloadClipsBookmarksCbox";
|
||||||
|
this.downloadClipsBookmarksCbox.Size = new System.Drawing.Size(248, 19);
|
||||||
|
this.downloadClipsBookmarksCbox.TabIndex = 20;
|
||||||
|
this.downloadClipsBookmarksCbox.Text = "Download Clips, Notes, and Bookmarks as";
|
||||||
|
this.downloadClipsBookmarksCbox.UseVisualStyleBackColor = true;
|
||||||
|
this.downloadClipsBookmarksCbox.CheckedChanged += new System.EventHandler(this.downloadClipsBookmarksCbox_CheckedChanged);
|
||||||
|
//
|
||||||
// audiobookFixupsGb
|
// audiobookFixupsGb
|
||||||
//
|
//
|
||||||
this.audiobookFixupsGb.Controls.Add(this.splitFilesByChapterCbox);
|
this.audiobookFixupsGb.Controls.Add(this.splitFilesByChapterCbox);
|
||||||
@ -656,7 +680,7 @@
|
|||||||
this.audiobookFixupsGb.Controls.Add(this.convertLosslessRb);
|
this.audiobookFixupsGb.Controls.Add(this.convertLosslessRb);
|
||||||
this.audiobookFixupsGb.Controls.Add(this.convertLossyRb);
|
this.audiobookFixupsGb.Controls.Add(this.convertLossyRb);
|
||||||
this.audiobookFixupsGb.Controls.Add(this.stripAudibleBrandingCbox);
|
this.audiobookFixupsGb.Controls.Add(this.stripAudibleBrandingCbox);
|
||||||
this.audiobookFixupsGb.Location = new System.Drawing.Point(6, 143);
|
this.audiobookFixupsGb.Location = new System.Drawing.Point(6, 169);
|
||||||
this.audiobookFixupsGb.Name = "audiobookFixupsGb";
|
this.audiobookFixupsGb.Name = "audiobookFixupsGb";
|
||||||
this.audiobookFixupsGb.Size = new System.Drawing.Size(403, 160);
|
this.audiobookFixupsGb.Size = new System.Drawing.Size(403, 160);
|
||||||
this.audiobookFixupsGb.TabIndex = 19;
|
this.audiobookFixupsGb.TabIndex = 19;
|
||||||
@ -1032,7 +1056,7 @@
|
|||||||
// mergeOpeningEndCreditsCbox
|
// mergeOpeningEndCreditsCbox
|
||||||
//
|
//
|
||||||
this.mergeOpeningEndCreditsCbox.AutoSize = true;
|
this.mergeOpeningEndCreditsCbox.AutoSize = true;
|
||||||
this.mergeOpeningEndCreditsCbox.Location = new System.Drawing.Point(19, 93);
|
this.mergeOpeningEndCreditsCbox.Location = new System.Drawing.Point(19, 118);
|
||||||
this.mergeOpeningEndCreditsCbox.Name = "mergeOpeningEndCreditsCbox";
|
this.mergeOpeningEndCreditsCbox.Name = "mergeOpeningEndCreditsCbox";
|
||||||
this.mergeOpeningEndCreditsCbox.Size = new System.Drawing.Size(198, 19);
|
this.mergeOpeningEndCreditsCbox.Size = new System.Drawing.Size(198, 19);
|
||||||
this.mergeOpeningEndCreditsCbox.TabIndex = 13;
|
this.mergeOpeningEndCreditsCbox.TabIndex = 13;
|
||||||
@ -1042,7 +1066,7 @@
|
|||||||
// retainAaxFileCbox
|
// retainAaxFileCbox
|
||||||
//
|
//
|
||||||
this.retainAaxFileCbox.AutoSize = true;
|
this.retainAaxFileCbox.AutoSize = true;
|
||||||
this.retainAaxFileCbox.Location = new System.Drawing.Point(19, 68);
|
this.retainAaxFileCbox.Location = new System.Drawing.Point(19, 93);
|
||||||
this.retainAaxFileCbox.Name = "retainAaxFileCbox";
|
this.retainAaxFileCbox.Name = "retainAaxFileCbox";
|
||||||
this.retainAaxFileCbox.Size = new System.Drawing.Size(132, 19);
|
this.retainAaxFileCbox.Size = new System.Drawing.Size(132, 19);
|
||||||
this.retainAaxFileCbox.TabIndex = 10;
|
this.retainAaxFileCbox.TabIndex = 10;
|
||||||
@ -1214,5 +1238,7 @@
|
|||||||
private System.Windows.Forms.GroupBox audiobookFixupsGb;
|
private System.Windows.Forms.GroupBox audiobookFixupsGb;
|
||||||
private System.Windows.Forms.CheckBox betaOptInCbox;
|
private System.Windows.Forms.CheckBox betaOptInCbox;
|
||||||
private System.Windows.Forms.CheckBox useCoverAsFolderIconCb;
|
private System.Windows.Forms.CheckBox useCoverAsFolderIconCb;
|
||||||
|
private System.Windows.Forms.ComboBox clipsBookmarksFormatCb;
|
||||||
|
private System.Windows.Forms.CheckBox downloadClipsBookmarksCbox;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,7 +78,7 @@ namespace LibationWinForms
|
|||||||
private void ToggleQueueHideBtn_Click(object sender, EventArgs e)
|
private void ToggleQueueHideBtn_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
SetQueueCollapseState(!splitContainer1.Panel2Collapsed);
|
SetQueueCollapseState(!splitContainer1.Panel2Collapsed);
|
||||||
Configuration.Instance.SetObject(nameof(splitContainer1.Panel2Collapsed), splitContainer1.Panel2Collapsed);
|
Configuration.Instance.SetNonString(splitContainer1.Panel2Collapsed, nameof(splitContainer1.Panel2Collapsed));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessBookQueue1_PopOut(object sender, EventArgs e)
|
private void ProcessBookQueue1_PopOut(object sender, EventArgs e)
|
||||||
|
|||||||
@ -56,9 +56,17 @@ namespace LibationWinForms
|
|||||||
AccountsSettingsPersister.Saving += accountsPreSave;
|
AccountsSettingsPersister.Saving += accountsPreSave;
|
||||||
AccountsSettingsPersister.Saved += accountsPostSave;
|
AccountsSettingsPersister.Saved += accountsPostSave;
|
||||||
|
|
||||||
|
Configuration.Instance.PropertyChanged += Configuration_PropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Configuration_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(Configuration.Instance.AutoScan))
|
||||||
|
{
|
||||||
// when autoscan setting is changed, update menu checkbox and run autoscan
|
// when autoscan setting is changed, update menu checkbox and run autoscan
|
||||||
Configuration.Instance.AutoScanChanged += updateAutoScanLibraryToolStripMenuItem;
|
updateAutoScanLibraryToolStripMenuItem(sender, e);
|
||||||
Configuration.Instance.AutoScanChanged += startAutoScan;
|
startAutoScan(sender, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
||||||
|
|||||||
@ -87,7 +87,7 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
saveState.IsMaximized = form.WindowState == FormWindowState.Maximized;
|
saveState.IsMaximized = form.WindowState == FormWindowState.Maximized;
|
||||||
|
|
||||||
config.SetObject(form.Name, saveState);
|
config.SetNonString(saveState, form.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,15 @@ namespace LibationWinForms.GridView
|
|||||||
&& updateReviewTask?.IsCompleted is not false)
|
&& updateReviewTask?.IsCompleted is not false)
|
||||||
{
|
{
|
||||||
updateReviewTask = UpdateRating(value);
|
updateReviewTask = UpdateRating(value);
|
||||||
|
updateReviewTask.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
if (t.Result)
|
||||||
|
{
|
||||||
|
_myRating = value;
|
||||||
|
LibraryBook.Book.UpdateUserDefinedItem(Book.UserDefinedItem.Tags, Book.UserDefinedItem.BookStatus, Book.UserDefinedItem.PdfStatus, value);
|
||||||
|
}
|
||||||
|
NotifyPropertyChanged();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,18 +89,12 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
#region User rating
|
#region User rating
|
||||||
|
|
||||||
private Task updateReviewTask;
|
private Task<bool> updateReviewTask;
|
||||||
private async Task UpdateRating(Rating rating)
|
private async Task<bool> UpdateRating(Rating rating)
|
||||||
{
|
{
|
||||||
var api = await LibraryBook.GetApiAsync();
|
var api = await LibraryBook.GetApiAsync();
|
||||||
|
|
||||||
if (await api.ReviewAsync(Book.AudibleProductId, (int)rating.OverallRating, (int)rating.PerformanceRating, (int)rating.StoryRating))
|
return await api.ReviewAsync(Book.AudibleProductId, (int)rating.OverallRating, (int)rating.PerformanceRating, (int)rating.StoryRating);
|
||||||
{
|
|
||||||
_myRating = rating;
|
|
||||||
LibraryBook.Book.UpdateUserDefinedItem(Book.UserDefinedItem.Tags, Book.UserDefinedItem.BookStatus, Book.UserDefinedItem.PdfStatus, rating);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.NotifyPropertyChanged(nameof(MyRating));
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user