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;
|
||||||
|
|||||||
@ -173,6 +173,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,7 +28,9 @@ 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 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 bool FixupFile { get; init; }
|
||||||
public NAudio.Lame.LameConfig LameConfig { get; init; }
|
public NAudio.Lame.LameConfig LameConfig { get; init; }
|
||||||
public bool Downsample { get; init; }
|
public bool Downsample { get; init; }
|
||||||
@ -32,15 +43,56 @@ namespace FileLiberator
|
|||||||
public string GetMultipartTitleName(MultiConvertFileProperties props)
|
public string GetMultipartTitleName(MultiConvertFileProperties props)
|
||||||
=> Templates.ChapterTitle.GetTitle(LibraryBookDto, props);
|
=> Templates.ChapterTitle.GetTitle(LibraryBookDto, props);
|
||||||
|
|
||||||
public DownloadOptions(LibraryBook libraryBook, string downloadUrl, string userAgent)
|
public async Task<string> SaveClipsAndBookmarks(string fileName)
|
||||||
{
|
{
|
||||||
LibraryBookDto = ArgumentValidator
|
if (DownloadClipsBookmarks)
|
||||||
.EnsureNotNull(libraryBook, nameof(libraryBook))
|
{
|
||||||
.ToDto();
|
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));
|
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
|
||||||
UserAgent = ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent));
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,16 +46,27 @@ namespace FileManager
|
|||||||
return stringCache[propertyName];
|
return stringCache[propertyName];
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetNonString<T>(string propertyName)
|
public T GetNonString<T>(string propertyName)
|
||||||
{
|
{
|
||||||
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 JObject jObject) return jObject.ToObject<T>();
|
|
||||||
return (T)obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
if (!objectCache.ContainsKey(propertyName))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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,7 +46,9 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
configuration.Reload();
|
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_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),
|
||||||
LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(),
|
LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(),
|
||||||
|
|||||||
@ -3,30 +3,51 @@ 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
|
||||||
{
|
{
|
||||||
public partial class Configuration
|
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
|
// 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.
|
// 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
|
// 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;
|
||||||
|
|
||||||
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
|
||||||
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
|
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)
|
if (settingWasChanged)
|
||||||
configuration?.Reload();
|
configuration?.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SettingsFilePath => Path.Combine(LibationFiles, "Settings.json");
|
public string SettingsFilePath => Path.Combine(LibationFiles, "Settings.json");
|
||||||
@ -45,161 +66,91 @@ 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); }
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(DownloadCoverArt));
|
[Description("Download clips and bookmarks?")]
|
||||||
set => persistentDictionary.SetNonString(nameof(DownloadCoverArt), value);
|
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.")]
|
[Description("Ask each time what action to take.")]
|
||||||
Ask = 0,
|
Ask = 0,
|
||||||
@ -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(() =>
|
||||||
|
{
|
||||||
|
c.Enabled = enabled;
|
||||||
|
c.Focus();
|
||||||
|
}));
|
||||||
else
|
else
|
||||||
|
{
|
||||||
c.Enabled = enabled;
|
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,12 +56,20 @@ namespace LibationWinForms
|
|||||||
AccountsSettingsPersister.Saving += accountsPreSave;
|
AccountsSettingsPersister.Saving += accountsPreSave;
|
||||||
AccountsSettingsPersister.Saved += accountsPostSave;
|
AccountsSettingsPersister.Saved += accountsPostSave;
|
||||||
|
|
||||||
// when autoscan setting is changed, update menu checkbox and run autoscan
|
Configuration.Instance.PropertyChanged += Configuration_PropertyChanged;
|
||||||
Configuration.Instance.AutoScanChanged += updateAutoScanLibraryToolStripMenuItem;
|
|
||||||
Configuration.Instance.AutoScanChanged += startAutoScan;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
private List<(string AccountId, string LocaleName)> getDefaultAccounts()
|
||||||
{
|
{
|
||||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||||
|
|||||||
@ -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