Compare commits

..

18 Commits

Author SHA1 Message Date
Robert McRackan
c437a39a82 New feature: download already split into chapters 2021-10-05 10:35:41 -04:00
Robert McRackan
7b55158148 Merge branch 'master' of https://github.com/rmcrackan/Libation 2021-10-05 10:08:21 -04:00
Robert McRackan
5772d9c31e defensive FirstOrDefault 2021-10-05 10:07:58 -04:00
rmcrackan
2a1f02b095 Merge pull request #127 from seanke/feature/multi_files
To mulitple files
2021-10-05 10:01:43 -04:00
Sean Kelly
5b7cde2a9e Fixed issues 2021-10-05 17:36:37 +13:00
Sean Kelly
5e349c6662 Removed repeated code 2021-09-30 20:32:30 +13:00
Sean Kelly
4b78b757aa Move files 2021-09-30 19:44:32 +13:00
Robert McRackan
22548dc8ae bug fix: if not importing episodes, remember to remove parents from import list 2021-09-29 10:00:04 -04:00
Robert McRackan
1165f81203 bug fix in series importer 2021-09-27 07:57:24 -04:00
Sean Kelly
13294d3414 Added m4b & mp3 methods for multiple files. 2021-09-27 21:34:43 +13:00
Sean Kelly
8a74a29700 Added configuration and wired it up. 2021-09-27 20:18:50 +13:00
Sean Kelly
36f58b64d6 proof of concept 2021-09-26 23:05:17 +13:00
Robert McRackan
19369a21ef * New feature: setting to not import episodes ( #125 ) 2021-09-25 14:02:27 -04:00
Robert McRackan
611fb4d6d8 increm ver 2021-09-24 20:11:52 -04:00
Robert McRackan
c77ec54035 bug fix: DownloadEpisodes logic needs parans 2021-09-24 19:59:57 -04:00
Robert McRackan
c9c28c7826 oops. again 2021-09-24 16:44:49 -04:00
Robert McRackan
30e2caaff5 New feature: setting to not download episodes 2021-09-24 16:44:28 -04:00
Robert McRackan
fd56017af5 dedicated lock objects for safety 2021-09-24 11:46:37 -04:00
24 changed files with 311 additions and 213 deletions

View File

@@ -15,7 +15,7 @@ namespace AaxDecrypter
private OutputFormat OutputFormat { get; }
public AaxcDownloadConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic, OutputFormat outputFormat)
public AaxcDownloadConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic, OutputFormat outputFormat, bool splitFileByChapters)
:base(outFileName, cacheDirectory, dlLic)
{
OutputFormat = outputFormat;
@@ -25,8 +25,12 @@ namespace AaxDecrypter
Name = "Download and Convert Aaxc To " + OutputFormat,
["Step 1: Get Aaxc Metadata"] = Step1_GetMetadata,
["Step 2: Download Decrypted Audiobook"] = Step2_DownloadAudiobook,
["Step 3: Create Cue"] = Step3_CreateCue,
["Step 2: Download Decrypted Audiobook"] = splitFileByChapters
? Step2_DownloadAudiobookAsMultipleFilesPerChapter
: Step2_DownloadAudiobookAsSingleFile,
["Step 3: Create Cue"] = splitFileByChapters
? () => true
: Step3_CreateCue,
["Step 4: Cleanup"] = Step4_Cleanup,
};
}
@@ -53,21 +57,10 @@ namespace AaxDecrypter
return !isCanceled;
}
protected override bool Step2_DownloadAudiobook()
protected override bool Step2_DownloadAudiobookAsSingleFile()
{
var zeroProgress = new DownloadProgress
{
BytesReceived = 0,
ProgressPercentage = 0,
TotalBytesToReceive = InputFileStream.Length
};
OnDecryptProgressUpdate(zeroProgress);
aaxFile.SetDecryptionKey(downloadLicense.AudibleKey, downloadLicense.AudibleIV);
var zeroProgress = Step2_Start();
if (File.Exists(outputFileName))
FileExt.SafeDelete(outputFileName);
@@ -77,15 +70,78 @@ namespace AaxDecrypter
var decryptionResult = OutputFormat == OutputFormat.M4b ? aaxFile.ConvertToMp4a(outputFile, downloadLicense.ChapterInfo) : aaxFile.ConvertToMp3(outputFile);
aaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate;
aaxFile.Close();
downloadLicense.ChapterInfo = aaxFile.Chapters;
Step2_End(zeroProgress);
return decryptionResult == ConversionResult.NoErrorsDetected && !isCanceled;
}
private bool Step2_DownloadAudiobookAsMultipleFilesPerChapter()
{
var zeroProgress = Step2_Start();
aaxFile.ConversionProgressUpdate += AaxFile_ConversionProgressUpdate;
if(OutputFormat == OutputFormat.M4b)
ConvertToMultiMp4b();
else
ConvertToMultiMp3();
aaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate;
Step2_End(zeroProgress);
return true;
}
private DownloadProgress Step2_Start()
{
var zeroProgress = new DownloadProgress
{
BytesReceived = 0,
ProgressPercentage = 0,
TotalBytesToReceive = InputFileStream.Length
};
OnDecryptProgressUpdate(zeroProgress);
aaxFile.SetDecryptionKey(downloadLicense.AudibleKey, downloadLicense.AudibleIV);
return zeroProgress;
}
private void Step2_End(DownloadProgress zeroProgress)
{
aaxFile.Close();
CloseInputFileStream();
OnDecryptProgressUpdate(zeroProgress);
}
return decryptionResult == ConversionResult.NoErrorsDetected && !isCanceled;
private void ConvertToMultiMp4b()
{
var chapterCount = 0;
aaxFile.ConvertToMultiMp4a(downloadLicense.ChapterInfo, newSplitCallback =>
{
chapterCount++;
var fileName = Path.ChangeExtension(outputFileName, $"{chapterCount}.m4b");
if (File.Exists(fileName))
FileExt.SafeDelete(fileName);
newSplitCallback.OutputFile = File.Open(fileName, FileMode.OpenOrCreate);
});
}
private void ConvertToMultiMp3()
{
var chapterCount = 0;
aaxFile.ConvertToMultiMp3(downloadLicense.ChapterInfo, newSplitCallback =>
{
chapterCount++;
var fileName = Path.ChangeExtension(outputFileName, $"{chapterCount}.mp3");
if (File.Exists(fileName))
FileExt.SafeDelete(fileName);
newSplitCallback.OutputFile = File.Open(fileName, FileMode.OpenOrCreate);
newSplitCallback.LameConfig.ID3.Track = chapterCount.ToString();
});
}
private void AaxFile_ConversionProgressUpdate(object sender, ConversionProgressEventArgs e)

View File

@@ -59,7 +59,7 @@ namespace AaxDecrypter
public abstract void Cancel();
protected abstract int GetSpeedup(TimeSpan elapsed);
protected abstract bool Step2_DownloadAudiobook();
protected abstract bool Step2_DownloadAudiobookAsSingleFile();
protected abstract bool Step1_GetMetadata();
public virtual void SetCoverArt(byte[] coverArt)
@@ -80,7 +80,7 @@ namespace AaxDecrypter
return false;
}
Serilog.Log.Logger.Information($"Speedup is {GetSpeedup(Elapsed)}x realtime.");
//Serilog.Log.Logger.Information($"Speedup is {GetSpeedup(Elapsed)}x realtime.");
return true;
}

View File

@@ -21,7 +21,7 @@ namespace AaxDecrypter
Name = "Download Mp3 Audiobook",
["Step 1: Get Mp3 Metadata"] = Step1_GetMetadata,
["Step 2: Download Audiobook"] = Step2_DownloadAudiobook,
["Step 2: Download Audiobook"] = Step2_DownloadAudiobookAsSingleFile,
["Step 3: Create Cue"] = Step3_CreateCue,
["Step 4: Cleanup"] = Step4_Cleanup,
};
@@ -46,7 +46,7 @@ namespace AaxDecrypter
return !isCanceled;
}
protected override bool Step2_DownloadAudiobook()
protected override bool Step2_DownloadAudiobookAsSingleFile()
{
DateTime startTime = DateTime.Now;

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Version>6.1.1.1</Version>
<Version>6.2.0.1</Version>
</PropertyGroup>
<ItemGroup>

View File

@@ -46,25 +46,23 @@ namespace AppScaffolding
return Configuration.Instance;
}
public static void RunPostConfigMigrations()
/// <summary>most migrations go in here</summary>
public static void RunPostConfigMigrations(Configuration config)
{
AudibleApiStorage.EnsureAccountsSettingsFileExists();
var config = Configuration.Instance;
//
// migrations go below here
//
Migrations.migrate_to_v5_2_0__post_config(config);
Migrations.migrate_to_v5_7_1(config);
Migrations.migrate_to_v6_1_2(config);
}
/// <summary>Initialize logging. Run after migration</summary>
public static void RunPostMigrationScaffolding()
public static void RunPostMigrationScaffolding(Configuration config)
{
var config = Configuration.Instance;
ensureSerilogConfig(config);
configureLogging(config);
logStartupState(config);
@@ -329,5 +327,15 @@ namespace AppScaffolding
if (!config.Exists(nameof(config.BadBook)))
config.BadBook = Configuration.BadBookAction.Ask;
}
// add config.DownloadEpisodes , config.ImportEpisodes
public static void migrate_to_v6_1_2(Configuration config)
{
if (!config.Exists(nameof(config.DownloadEpisodes)))
config.DownloadEpisodes = true;
if (!config.Exists(nameof(config.ImportEpisodes)))
config.ImportEpisodes = true;
}
}
}

View File

@@ -14,15 +14,13 @@ namespace ApplicationServices
{
public static class LibraryCommands
{
private static LibraryOptions.ResponseGroupOptions LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS;
public static async Task<List<LibraryBook>> FindInactiveBooks(Func<Account, Task<ApiExtended>> apiExtendedfunc, List<LibraryBook> existingLibrary, params Account[] accounts)
{
logRestart();
//These are the minimum response groups required for the
//library scanner to pass all validation and filtering.
LibraryResponseGroups =
var libraryResponseGroups =
LibraryOptions.ResponseGroupOptions.ProductAttrs |
LibraryOptions.ResponseGroupOptions.ProductDesc |
LibraryOptions.ResponseGroupOptions.Relationships;
@@ -33,7 +31,7 @@ namespace ApplicationServices
try
{
logTime($"pre {nameof(scanAccountsAsync)} all");
var libraryItems = await scanAccountsAsync(apiExtendedfunc, accounts);
var libraryItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryResponseGroups);
logTime($"post {nameof(scanAccountsAsync)} all");
var totalCount = libraryItems.Count;
@@ -68,7 +66,6 @@ namespace ApplicationServices
}
finally
{
LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS;
stop();
var putBreakPointHere = logOutput;
}
@@ -85,7 +82,7 @@ namespace ApplicationServices
try
{
logTime($"pre {nameof(scanAccountsAsync)} all");
var importItems = await scanAccountsAsync(apiExtendedfunc, accounts);
var importItems = await scanAccountsAsync(apiExtendedfunc, accounts, LibraryOptions.ResponseGroupOptions.ALL_OPTIONS);
logTime($"post {nameof(scanAccountsAsync)} all");
var totalCount = importItems.Count;
@@ -129,7 +126,7 @@ namespace ApplicationServices
}
}
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts)
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts, LibraryOptions.ResponseGroupOptions libraryResponseGroups)
{
var tasks = new List<Task<List<ImportItem>>>();
foreach (var account in accounts)
@@ -138,7 +135,7 @@ namespace ApplicationServices
var apiExtended = await apiExtendedfunc(account);
// add scanAccountAsync as a TASK: do not await
tasks.Add(scanAccountAsync(apiExtended, account));
tasks.Add(scanAccountAsync(apiExtended, account, libraryResponseGroups));
}
// import library in parallel
@@ -147,7 +144,7 @@ namespace ApplicationServices
return importItems;
}
private static async Task<List<ImportItem>> scanAccountAsync(ApiExtended apiExtended, Account account)
private static async Task<List<ImportItem>> scanAccountAsync(ApiExtended apiExtended, Account account, LibraryOptions.ResponseGroupOptions libraryResponseGroups)
{
ArgumentValidator.EnsureNotNull(account, nameof(account));
@@ -158,7 +155,7 @@ namespace ApplicationServices
logTime($"pre scanAccountAsync {account.AccountName}");
var dtoItems = await apiExtended.GetLibraryValidatedAsync(LibraryResponseGroups);
var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryResponseGroups, FileManager.Configuration.Instance.ImportEpisodes);
logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}");

View File

@@ -66,9 +66,11 @@ namespace DtoImporterService
Category parentCategory = null;
if (i == 1)
parentCategory = DbContext.Categories.Local.Single(c => c.AudibleCategoryId == pair[0].CategoryId);
// should be "Single()" but user is getting a strange error
parentCategory = DbContext.Categories.Local.FirstOrDefault(c => c.AudibleCategoryId == pair[0].CategoryId);
var category = DbContext.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == id);
// should be "SingleOrDefault()" but user is getting a strange error
var category = DbContext.Categories.Local.FirstOrDefault(c => c.AudibleCategoryId == id);
if (category is null)
{
category = DbContext.Categories.Add(new Category(new AudibleCategoryId(id), name)).Entity;

View File

@@ -48,7 +48,7 @@ namespace DtoImporterService
foreach (var s in requestedSeries)
{
var series = DbContext.Series.Local.SingleOrDefault(c => c.AudibleSeriesId == s.SeriesId);
var series = DbContext.Series.Local.FirstOrDefault(c => c.AudibleSeriesId == s.SeriesId);
if (series is null)
{
series = DbContext.Series.Add(new DataLayer.Series(new AudibleSeriesId(s.SeriesId))).Entity;

View File

@@ -95,10 +95,12 @@ namespace FileLiberator
foreach (var chap in contentLic.ContentMetadata?.ChapterInfo?.Chapters)
audiobookDlLic.ChapterInfo.AddChapter(chap.Title, TimeSpan.FromMilliseconds(chap.LengthMs));
}
var outFileName = Path.Combine(destinationDir, $"{PathLib.ToPathSafeString(libraryBook.Book.Title)} [{libraryBook.Book.AudibleProductId}].{outputFormat.ToString().ToLower()}");
aaxcDownloader = contentLic.DrmType == AudibleApi.Common.DrmType.Adrm ? new AaxcDownloadConverter(outFileName, cacheDir, audiobookDlLic, outputFormat) { AppName = "Libation" } : new UnencryptedAudiobookDownloader(outFileName, cacheDir, audiobookDlLic);
aaxcDownloader = contentLic.DrmType == AudibleApi.Common.DrmType.Adrm
? new AaxcDownloadConverter(outFileName, cacheDir, audiobookDlLic, outputFormat, Configuration.Instance.SplitFilesByChapter) { AppName = "Libation" }
: new UnencryptedAudiobookDownloader(outFileName, cacheDir, audiobookDlLic);
aaxcDownloader.DecryptProgressUpdate += (s, progress) => StreamingProgressChanged?.Invoke(this, progress);
aaxcDownloader.DecryptTimeRemaining += (s, remaining) => StreamingTimeRemaining?.Invoke(this, remaining);
aaxcDownloader.RetrievedTitle += (s, title) => TitleDiscovered?.Invoke(this, title);
@@ -138,7 +140,7 @@ namespace FileLiberator
{
// create final directory. move each file into it. MOVE AUDIO FILE LAST
// new dir: safetitle_limit50char + " [" + productId + "]"
// TODO make this method handle multiple audio files or a single audio file.
var destinationDir = AudibleFileStorage.Audio.GetDestDir(product.Title, product.AudibleProductId);
Directory.CreateDirectory(destinationDir);
@@ -153,9 +155,9 @@ namespace FileLiberator
foreach (var f in sortedFiles)
{
var dest
= AudibleFileStorage.Audio.IsFileTypeMatch(f)
? audioFileName
// non-audio filename: safetitle_limit50char + " [" + productId + "][" + audio_ext +"]." + non_audio_ext
= AudibleFileStorage.Audio.IsFileTypeMatch(f)//f.Extension.Equals($".{musicFileExt}", StringComparison.OrdinalIgnoreCase)
? Path.Join(destinationDir, f.Name)
// non-audio filename: safetitle_limit50char + " [" + productId + "][" + audio_ext + "]." + non_audio_ext
: FileUtility.GetValidFilename(destinationDir, product.Title, f.Extension, product.AudibleProductId, musicFileExt);
if (Path.GetExtension(dest).Trim('.').ToLower() == "cue")

View File

@@ -11,21 +11,40 @@ using FileManager;
namespace FileLiberator
{
public class DownloadPdf : DownloadableBase
public class DownloadPdf : IProcessable
{
public override bool Validate(LibraryBook libraryBook)
public event EventHandler<LibraryBook> Begin;
public event EventHandler<LibraryBook> Completed;
public event EventHandler<string> StreamingBegin;
public event EventHandler<DownloadProgress> StreamingProgressChanged;
public event EventHandler<string> StreamingCompleted;
public event EventHandler<string> StatusUpdate;
public event EventHandler<TimeSpan> StreamingTimeRemaining;
public bool Validate(LibraryBook libraryBook)
=> !string.IsNullOrWhiteSpace(getdownloadUrl(libraryBook))
&& !libraryBook.Book.PDF_Exists;
public override async Task<StatusHandler> ProcessItemAsync(LibraryBook libraryBook)
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{
var proposedDownloadFilePath = getProposedDownloadFilePath(libraryBook);
var actualDownloadedFilePath = await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
var result = verifyDownload(actualDownloadedFilePath);
Begin?.Invoke(this, libraryBook);
libraryBook.Book.UserDefinedItem.PdfStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
try
{
var proposedDownloadFilePath = getProposedDownloadFilePath(libraryBook);
var actualDownloadedFilePath = await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
var result = verifyDownload(actualDownloadedFilePath);
return result;
libraryBook.Book.UserDefinedItem.PdfStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
return result;
}
finally
{
Completed?.Invoke(this, libraryBook);
}
}
private static string getProposedDownloadFilePath(LibraryBook libraryBook)
@@ -50,15 +69,26 @@ namespace FileLiberator
private async Task<string> downloadPdfAsync(LibraryBook libraryBook, string proposedDownloadFilePath)
{
var api = await libraryBook.GetApiAsync();
var downloadUrl = await api.GetPdfDownloadLinkAsync(libraryBook.Book.AudibleProductId);
StreamingBegin?.Invoke(this, proposedDownloadFilePath);
var client = new HttpClient();
var actualDownloadedFilePath = await PerformDownloadAsync(
proposedDownloadFilePath,
(p) => client.DownloadFileAsync(downloadUrl, proposedDownloadFilePath, p));
try
{
var api = await libraryBook.GetApiAsync();
var downloadUrl = await api.GetPdfDownloadLinkAsync(libraryBook.Book.AudibleProductId);
return actualDownloadedFilePath;
var progress = new Progress<DownloadProgress>();
progress.ProgressChanged += (_, e) => StreamingProgressChanged?.Invoke(this, e);
var client = new HttpClient();
var actualDownloadedFilePath = await client.DownloadFileAsync(downloadUrl, proposedDownloadFilePath, progress);
StatusUpdate?.Invoke(this, actualDownloadedFilePath);
return actualDownloadedFilePath;
}
finally
{
StreamingCompleted?.Invoke(this, proposedDownloadFilePath);
}
}
private static StatusHandler verifyDownload(string actualDownloadedFilePath)

View File

@@ -1,63 +0,0 @@
using System;
using System.Threading.Tasks;
using DataLayer;
using Dinah.Core.ErrorHandling;
using Dinah.Core.Net.Http;
namespace FileLiberator
{
public abstract class DownloadableBase : IProcessable
{
public event EventHandler<LibraryBook> Begin;
public event EventHandler<LibraryBook> Completed;
public event EventHandler<string> StreamingBegin;
public event EventHandler<DownloadProgress> StreamingProgressChanged;
public event EventHandler<string> StreamingCompleted;
public event EventHandler<string> StatusUpdate;
public event EventHandler<TimeSpan> StreamingTimeRemaining;
protected void Invoke_StatusUpdate(string message) => StatusUpdate?.Invoke(this, message);
public abstract bool Validate(LibraryBook libraryBook);
public abstract Task<StatusHandler> ProcessItemAsync(LibraryBook libraryBook);
// do NOT use ConfigureAwait(false) on ProcessAsync()
// often calls events which prints to forms in the UI context
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{
Begin?.Invoke(this, libraryBook);
try
{
return await ProcessItemAsync(libraryBook);
}
finally
{
Completed?.Invoke(this, libraryBook);
}
}
protected async Task<string> PerformDownloadAsync(string proposedDownloadFilePath, Func<Progress<DownloadProgress>, Task<string>> func)
{
var progress = new Progress<DownloadProgress>();
progress.ProgressChanged += (_, e) => StreamingProgressChanged?.Invoke(this, e);
StreamingBegin?.Invoke(this, proposedDownloadFilePath);
try
{
var result = await func(progress);
StatusUpdate?.Invoke(this, result);
return result;
}
finally
{
StreamingCompleted?.Invoke(this, proposedDownloadFilePath);
}
}
}
}

View File

@@ -10,15 +10,12 @@ namespace FileLiberator
{
public static class IProcessableExt
{
//
// DO NOT USE ConfigureAwait(false) WITH ProcessAsync() unless ensuring ProcessAsync() implementation is cross-thread compatible
// ProcessAsync() often does a lot with forms in the UI context
//
// when used in foreach: stateful. deferred execution
public static IEnumerable<LibraryBook> GetValidLibraryBooks(this IProcessable processable, IEnumerable<LibraryBook> library)
=> library.Where(libraryBook => processable.Validate(libraryBook));
=> library.Where(libraryBook =>
processable.Validate(libraryBook)
&& (libraryBook.Book.ContentType != ContentType.Episode || FileManager.Configuration.Instance.DownloadEpisodes)
);
public static async Task<StatusHandler> ProcessSingleAsync(this IProcessable processable, LibraryBook libraryBook, bool validate)
{

View File

@@ -36,6 +36,7 @@ namespace FileManager
}
}
private static object bookDirectoryFilesLocker { get; } = new();
internal static BackgroundFileSystem BookDirectoryFiles { get; set; }
#endregion
@@ -47,7 +48,7 @@ namespace FileManager
protected AudibleFileStorage(FileType fileType) : base((int)fileType, fileType.ToString())
{
extensions_noDots = Extensions.Select(ext => ext.Trim('.')).ToList();
extensions_noDots = Extensions.Select(ext => ext.ToLower().Trim('.')).ToList();
extAggr = extensions_noDots.Aggregate((a, b) => $"{a}|{b}");
BookDirectoryFiles ??= new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
}
@@ -58,7 +59,8 @@ namespace FileManager
if (cachedFile != null)
return cachedFile;
string regexPattern = $@"{productId}.*?\.({extAggr})$";
var regex = new Regex($@"{productId}.*?\.({extAggr})$", RegexOptions.IgnoreCase);
string firstOrNull;
if (StorageDirectory == BooksDirectory)
@@ -66,7 +68,7 @@ namespace FileManager
//If user changed the BooksDirectory, reinitialize.
if (StorageDirectory != BookDirectoryFiles.RootDirectory)
{
lock (BookDirectoryFiles)
lock (bookDirectoryFilesLocker)
{
if (StorageDirectory != BookDirectoryFiles.RootDirectory)
{
@@ -75,14 +77,14 @@ namespace FileManager
}
}
firstOrNull = BookDirectoryFiles.FindFile(regexPattern, RegexOptions.IgnoreCase);
firstOrNull = BookDirectoryFiles.FindFile(regex);
}
else
{
firstOrNull =
Directory
.EnumerateFiles(StorageDirectory, "*.*", SearchOption.AllDirectories)
.FirstOrDefault(s => Regex.IsMatch(s, regexPattern, RegexOptions.IgnoreCase));
.FirstOrDefault(s => regex.IsMatch(s));
}
if (firstOrNull is null)

View File

@@ -3,8 +3,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace FileManager
@@ -25,6 +23,8 @@ namespace FileManager
private FileSystemWatcher fileSystemWatcher { get; set; }
private BlockingCollection<FileSystemEventArgs> directoryChangesEvents { get; set; }
private Task backgroundScanner { get; set; }
private object fsCacheLocker { get; } = new();
private List<string> fsCache { get; } = new();
public BackgroundFileSystem(string rootDirectory, string searchPattern, SearchOption searchOptions)
@@ -36,17 +36,15 @@ namespace FileManager
Init();
}
public string FindFile(string regexPattern, RegexOptions options)
public string FindFile(System.Text.RegularExpressions.Regex regex)
{
lock (fsCache)
{
return fsCache.FirstOrDefault(s => Regex.IsMatch(s, regexPattern, options));
}
lock (fsCacheLocker)
return fsCache.FirstOrDefault(s => regex.IsMatch(s));
}
public void RefreshFiles()
{
lock (fsCache)
lock (fsCacheLocker)
{
fsCache.Clear();
fsCache.AddRange(Directory.EnumerateFiles(RootDirectory, SearchPattern, SearchOption));
@@ -57,17 +55,19 @@ namespace FileManager
{
Stop();
lock (fsCache)
lock (fsCacheLocker)
fsCache.AddRange(Directory.EnumerateFiles(RootDirectory, SearchPattern, SearchOption));
directoryChangesEvents = new BlockingCollection<FileSystemEventArgs>();
fileSystemWatcher = new FileSystemWatcher(RootDirectory);
fileSystemWatcher.Created += FileSystemWatcher_Changed;
fileSystemWatcher = new FileSystemWatcher(RootDirectory)
{
IncludeSubdirectories = true,
EnableRaisingEvents = true
};
fileSystemWatcher.Created += FileSystemWatcher_Changed;
fileSystemWatcher.Deleted += FileSystemWatcher_Changed;
fileSystemWatcher.Renamed += FileSystemWatcher_Changed;
fileSystemWatcher.Error += FileSystemWatcher_Error;
fileSystemWatcher.IncludeSubdirectories = true;
fileSystemWatcher.EnableRaisingEvents = true;
backgroundScanner = new Task(BackgroundScanner);
backgroundScanner.Start();
@@ -86,7 +86,7 @@ namespace FileManager
//Dispose of directoryChangesEvents after backgroundScanner exists.
directoryChangesEvents?.Dispose();
lock (fsCache)
lock (fsCacheLocker)
fsCache.Clear();
}
@@ -106,7 +106,7 @@ namespace FileManager
{
while (directoryChangesEvents.TryTake(out FileSystemEventArgs change, -1))
{
lock (fsCache)
lock (fsCacheLocker)
UpdateLocalCache(change);
}
}
@@ -146,9 +146,7 @@ namespace FileManager
private void AddUniqueFiles(IEnumerable<string> newFiles)
{
foreach (var file in newFiles)
{
AddUniqueFile(file);
}
}
private void AddUniqueFile(string newFile)
{

View File

@@ -101,6 +101,13 @@ namespace FileManager
get => persistentDictionary.GetNonString<bool>(nameof(DecryptToLossy));
set => persistentDictionary.SetNonString(nameof(DecryptToLossy), value);
}
[Description("Split my books into multi files by cahpter")]
public bool SplitFilesByChapter
{
get => persistentDictionary.GetNonString<bool>(nameof(SplitFilesByChapter));
set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value);
}
public enum BadBookAction
{
@@ -125,6 +132,20 @@ namespace FileManager
set => persistentDictionary.SetString(nameof(BadBook), value.ToString());
}
[Description("Import episodes? (eg: podcasts) When unchecked, episodes will not be imported into Libation.")]
public bool ImportEpisodes
{
get => persistentDictionary.GetNonString<bool>(nameof(ImportEpisodes));
set => persistentDictionary.SetNonString(nameof(ImportEpisodes), value);
}
[Description("Download episodes? (eg: podcasts). When unchecked, episodes already in Libation will not be downloaded.")]
public bool DownloadEpisodes
{
get => persistentDictionary.GetNonString<bool>(nameof(DownloadEpisodes));
set => persistentDictionary.SetNonString(nameof(DownloadEpisodes), value);
}
#endregion
#region known directories

View File

@@ -13,7 +13,7 @@ namespace FileManager
// file max length = 255. dir max len = 247
// sanitize
filename = GetAsciiTag(filename);
filename = getAsciiTag(filename);
// manage length
if (filename.Length > 50)
filename = filename.Substring(0, 50) + "[...]";
@@ -35,7 +35,7 @@ namespace FileManager
return fullfilename;
}
public static string GetAsciiTag(string property)
private static string getAsciiTag(string property)
{
if (property == null)
return "";

View File

@@ -43,11 +43,12 @@ namespace FileManager
public static event EventHandler<PictureCachedEventArgs> PictureCached;
private static BlockingCollection<PictureDefinition> DownloadQueue { get; } = new BlockingCollection<PictureDefinition>();
private static object cacheLocker { get; } = new object();
private static Dictionary<PictureDefinition, byte[]> cache { get; } = new Dictionary<PictureDefinition, byte[]>();
private static Dictionary<PictureSize, byte[]> defaultImages { get; } = new Dictionary<PictureSize, byte[]>();
public static (bool isDefault, byte[] bytes) GetPicture(PictureDefinition def)
{
lock (cache)
lock (cacheLocker)
{
if (cache.ContainsKey(def))
return (false, cache[def]);
@@ -67,7 +68,7 @@ namespace FileManager
public static byte[] GetPictureSynchronously(PictureDefinition def)
{
lock (cache)
lock (cacheLocker)
{
if (!cache.ContainsKey(def) || cache[def] == null)
{
@@ -104,7 +105,7 @@ namespace FileManager
var bytes = downloadBytes(def);
saveFile(def, bytes);
lock (cache)
lock (cacheLocker)
cache[def] = bytes;
PictureCached?.Invoke(nameof(PictureStorage), new PictureCachedEventArgs { Definition = def, Picture = bytes });

View File

@@ -106,16 +106,16 @@ namespace InternalUtilities
// 2 retries == 3 total
.RetryAsync(2);
public Task<List<Item>> GetLibraryValidatedAsync(LibraryOptions.ResponseGroupOptions responseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS)
public Task<List<Item>> GetLibraryValidatedAsync(LibraryOptions.ResponseGroupOptions responseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS, bool importEpisodes = true)
{
// bug on audible's side. the 1st time after a long absence, a query to get library will return without titles or authors. a subsequent identical query will be successful. this is true whether or tokens are refreshed
// bug on audible's side. the 1st time after a long absence, a query to get library will return without titles or authors. a subsequent identical query will be successful. this is true whether or not tokens are refreshed
// worse, this 1st dummy call doesn't seem to help:
// var page = await api.GetLibraryAsync(new AudibleApi.LibraryOptions { NumberOfResultPerPage = 1, PageNumber = 1, PurchasedAfter = DateTime.Now.AddYears(-20), ResponseGroups = AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS });
// i don't want to incur the cost of making a full dummy call every time because it fails sometimes
return policy.ExecuteAsync(() => getItemsAsync(responseGroups));
return policy.ExecuteAsync(() => getItemsAsync(responseGroups, importEpisodes));
}
private async Task<List<Item>> getItemsAsync(LibraryOptions.ResponseGroupOptions responseGroups)
private async Task<List<Item>> getItemsAsync(LibraryOptions.ResponseGroupOptions responseGroups, bool importEpisodes)
{
var items = new List<Item>();
#if DEBUG
@@ -130,7 +130,7 @@ namespace InternalUtilities
if (!items.Any())
items = await Api.GetAllLibraryItemsAsync(responseGroups);
await manageEpisodesAsync(items);
await manageEpisodesAsync(items, importEpisodes);
#if DEBUG
//System.IO.File.WriteAllText(library_json, AudibleApi.Common.Converter.ToJson(items));
@@ -149,7 +149,7 @@ namespace InternalUtilities
}
#region episodes and podcasts
private async Task manageEpisodesAsync(List<Item> items)
private async Task manageEpisodesAsync(List<Item> items, bool importEpisodes)
{
// add podcasts and episodes to list. If fail, don't let it de-rail the rest of the import
try
@@ -170,10 +170,13 @@ namespace InternalUtilities
// also must happen before processing children because children abuses this flag
items.RemoveAll(i => i.IsEpisodes);
// add children
var children = await getEpisodesAsync(parents);
Serilog.Log.Logger.Information($"{children.Count} episodes of shows/podcasts found");
items.AddRange(children);
if (importEpisodes)
{
// add children
var children = await getEpisodesAsync(parents);
Serilog.Log.Logger.Information($"{children.Count} episodes of shows/podcasts found");
items.AddRange(children);
}
}
catch (Exception ex)
{

View File

@@ -24,8 +24,8 @@ namespace LibationCli
var config = LibationScaffolding.RunPreConfigMigrations();
LibationScaffolding.RunPostConfigMigrations();
LibationScaffolding.RunPostMigrationScaffolding();
LibationScaffolding.RunPostConfigMigrations(config);
LibationScaffolding.RunPostMigrationScaffolding(config);
#if !DEBUG
checkForUpdate();

View File

@@ -41,7 +41,7 @@ namespace LibationWinForms.Dialogs
this.directoryComboBox.FormattingEnabled = true;
this.directoryComboBox.Location = new System.Drawing.Point(0, 0);
this.directoryComboBox.Name = "directoryComboBox";
this.directoryComboBox.Size = new System.Drawing.Size(647, 23);
this.directoryComboBox.Size = new System.Drawing.Size(407, 23);
this.directoryComboBox.TabIndex = 0;
this.directoryComboBox.SelectedIndexChanged += new System.EventHandler(this.directoryComboBox_SelectedIndexChanged);
//
@@ -52,7 +52,7 @@ namespace LibationWinForms.Dialogs
this.textBox1.Location = new System.Drawing.Point(0, 29);
this.textBox1.Name = "textBox1";
this.textBox1.ReadOnly = true;
this.textBox1.Size = new System.Drawing.Size(647, 23);
this.textBox1.Size = new System.Drawing.Size(407, 23);
this.textBox1.TabIndex = 1;
//
// DirectorySelectControl
@@ -62,7 +62,7 @@ namespace LibationWinForms.Dialogs
this.Controls.Add(this.textBox1);
this.Controls.Add(this.directoryComboBox);
this.Name = "DirectorySelectControl";
this.Size = new System.Drawing.Size(647, 52);
this.Size = new System.Drawing.Size(407, 52);
this.Load += new System.EventHandler(this.DirectorySelectControl_Load);
this.ResumeLayout(false);
this.PerformLayout();

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">

View File

@@ -33,12 +33,15 @@
this.saveBtn = new System.Windows.Forms.Button();
this.cancelBtn = new System.Windows.Forms.Button();
this.advancedSettingsGb = new System.Windows.Forms.GroupBox();
this.importEpisodesCb = new System.Windows.Forms.CheckBox();
this.downloadEpisodesCb = new System.Windows.Forms.CheckBox();
this.badBookGb = new System.Windows.Forms.GroupBox();
this.badBookIgnoreRb = new System.Windows.Forms.RadioButton();
this.badBookRetryRb = new System.Windows.Forms.RadioButton();
this.badBookAbortRb = new System.Windows.Forms.RadioButton();
this.badBookAskRb = new System.Windows.Forms.RadioButton();
this.decryptAndConvertGb = new System.Windows.Forms.GroupBox();
this.splitFilesByChapterCbox = new System.Windows.Forms.CheckBox();
this.allowLibationFixupCbox = new System.Windows.Forms.CheckBox();
this.convertLossyRb = new System.Windows.Forms.RadioButton();
this.convertLosslessRb = new System.Windows.Forms.RadioButton();
@@ -67,21 +70,21 @@
// inProgressDescLbl
//
this.inProgressDescLbl.AutoSize = true;
this.inProgressDescLbl.Location = new System.Drawing.Point(8, 149);
this.inProgressDescLbl.Location = new System.Drawing.Point(8, 199);
this.inProgressDescLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.inProgressDescLbl.Name = "inProgressDescLbl";
this.inProgressDescLbl.Size = new System.Drawing.Size(43, 45);
this.inProgressDescLbl.TabIndex = 15;
this.inProgressDescLbl.TabIndex = 18;
this.inProgressDescLbl.Text = "[desc]\r\n[line 2]\r\n[line 3]";
//
// saveBtn
//
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.saveBtn.Location = new System.Drawing.Point(714, 445);
this.saveBtn.Location = new System.Drawing.Point(714, 496);
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.saveBtn.Name = "saveBtn";
this.saveBtn.Size = new System.Drawing.Size(88, 27);
this.saveBtn.TabIndex = 17;
this.saveBtn.TabIndex = 98;
this.saveBtn.Text = "Save";
this.saveBtn.UseVisualStyleBackColor = true;
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
@@ -90,11 +93,11 @@
//
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.cancelBtn.Location = new System.Drawing.Point(832, 445);
this.cancelBtn.Location = new System.Drawing.Point(832, 496);
this.cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.cancelBtn.Name = "cancelBtn";
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
this.cancelBtn.TabIndex = 18;
this.cancelBtn.TabIndex = 99;
this.cancelBtn.Text = "Cancel";
this.cancelBtn.UseVisualStyleBackColor = true;
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
@@ -104,6 +107,8 @@
this.advancedSettingsGb.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.advancedSettingsGb.Controls.Add(this.importEpisodesCb);
this.advancedSettingsGb.Controls.Add(this.downloadEpisodesCb);
this.advancedSettingsGb.Controls.Add(this.badBookGb);
this.advancedSettingsGb.Controls.Add(this.decryptAndConvertGb);
this.advancedSettingsGb.Controls.Add(this.inProgressSelectControl);
@@ -112,21 +117,41 @@
this.advancedSettingsGb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.advancedSettingsGb.Name = "advancedSettingsGb";
this.advancedSettingsGb.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.advancedSettingsGb.Size = new System.Drawing.Size(908, 258);
this.advancedSettingsGb.Size = new System.Drawing.Size(908, 309);
this.advancedSettingsGb.TabIndex = 6;
this.advancedSettingsGb.TabStop = false;
this.advancedSettingsGb.Text = "Advanced settings for control freaks";
//
// importEpisodesCb
//
this.importEpisodesCb.AutoSize = true;
this.importEpisodesCb.Location = new System.Drawing.Point(7, 22);
this.importEpisodesCb.Name = "importEpisodesCb";
this.importEpisodesCb.Size = new System.Drawing.Size(146, 19);
this.importEpisodesCb.TabIndex = 7;
this.importEpisodesCb.Text = "[import episodes desc]";
this.importEpisodesCb.UseVisualStyleBackColor = true;
//
// downloadEpisodesCb
//
this.downloadEpisodesCb.AutoSize = true;
this.downloadEpisodesCb.Location = new System.Drawing.Point(7, 47);
this.downloadEpisodesCb.Name = "downloadEpisodesCb";
this.downloadEpisodesCb.Size = new System.Drawing.Size(163, 19);
this.downloadEpisodesCb.TabIndex = 8;
this.downloadEpisodesCb.Text = "[download episodes desc]";
this.downloadEpisodesCb.UseVisualStyleBackColor = true;
//
// badBookGb
//
this.badBookGb.Controls.Add(this.badBookIgnoreRb);
this.badBookGb.Controls.Add(this.badBookRetryRb);
this.badBookGb.Controls.Add(this.badBookAbortRb);
this.badBookGb.Controls.Add(this.badBookAskRb);
this.badBookGb.Location = new System.Drawing.Point(372, 22);
this.badBookGb.Location = new System.Drawing.Point(372, 72);
this.badBookGb.Name = "badBookGb";
this.badBookGb.Size = new System.Drawing.Size(529, 124);
this.badBookGb.TabIndex = 11;
this.badBookGb.TabIndex = 13;
this.badBookGb.TabStop = false;
this.badBookGb.Text = "[bad book desc]";
//
@@ -136,7 +161,7 @@
this.badBookIgnoreRb.Location = new System.Drawing.Point(6, 97);
this.badBookIgnoreRb.Name = "badBookIgnoreRb";
this.badBookIgnoreRb.Size = new System.Drawing.Size(94, 19);
this.badBookIgnoreRb.TabIndex = 15;
this.badBookIgnoreRb.TabIndex = 17;
this.badBookIgnoreRb.TabStop = true;
this.badBookIgnoreRb.Text = "[ignore desc]";
this.badBookIgnoreRb.UseVisualStyleBackColor = true;
@@ -147,7 +172,7 @@
this.badBookRetryRb.Location = new System.Drawing.Point(6, 72);
this.badBookRetryRb.Name = "badBookRetryRb";
this.badBookRetryRb.Size = new System.Drawing.Size(84, 19);
this.badBookRetryRb.TabIndex = 14;
this.badBookRetryRb.TabIndex = 16;
this.badBookRetryRb.TabStop = true;
this.badBookRetryRb.Text = "[retry desc]";
this.badBookRetryRb.UseVisualStyleBackColor = true;
@@ -158,7 +183,7 @@
this.badBookAbortRb.Location = new System.Drawing.Point(6, 47);
this.badBookAbortRb.Name = "badBookAbortRb";
this.badBookAbortRb.Size = new System.Drawing.Size(88, 19);
this.badBookAbortRb.TabIndex = 13;
this.badBookAbortRb.TabIndex = 15;
this.badBookAbortRb.TabStop = true;
this.badBookAbortRb.Text = "[abort desc]";
this.badBookAbortRb.UseVisualStyleBackColor = true;
@@ -169,23 +194,34 @@
this.badBookAskRb.Location = new System.Drawing.Point(6, 22);
this.badBookAskRb.Name = "badBookAskRb";
this.badBookAskRb.Size = new System.Drawing.Size(77, 19);
this.badBookAskRb.TabIndex = 12;
this.badBookAskRb.TabIndex = 14;
this.badBookAskRb.TabStop = true;
this.badBookAskRb.Text = "[ask desc]";
this.badBookAskRb.UseVisualStyleBackColor = true;
//
// decryptAndConvertGb
//
this.decryptAndConvertGb.Controls.Add(this.splitFilesByChapterCbox);
this.decryptAndConvertGb.Controls.Add(this.allowLibationFixupCbox);
this.decryptAndConvertGb.Controls.Add(this.convertLossyRb);
this.decryptAndConvertGb.Controls.Add(this.convertLosslessRb);
this.decryptAndConvertGb.Location = new System.Drawing.Point(7, 22);
this.decryptAndConvertGb.Location = new System.Drawing.Point(7, 72);
this.decryptAndConvertGb.Name = "decryptAndConvertGb";
this.decryptAndConvertGb.Size = new System.Drawing.Size(359, 124);
this.decryptAndConvertGb.TabIndex = 7;
this.decryptAndConvertGb.TabIndex = 9;
this.decryptAndConvertGb.TabStop = false;
this.decryptAndConvertGb.Text = "Decrypt and convert";
//
// splitFilesByChapterCbox
//
this.splitFilesByChapterCbox.AutoSize = true;
this.splitFilesByChapterCbox.Location = new System.Drawing.Point(6, 46);
this.splitFilesByChapterCbox.Name = "splitFilesByChapterCbox";
this.splitFilesByChapterCbox.Size = new System.Drawing.Size(258, 19);
this.splitFilesByChapterCbox.TabIndex = 13;
this.splitFilesByChapterCbox.Text = "Split my books into multiple files by chapter";
this.splitFilesByChapterCbox.UseVisualStyleBackColor = true;
//
// allowLibationFixupCbox
//
this.allowLibationFixupCbox.AutoSize = true;
@@ -194,7 +230,7 @@
this.allowLibationFixupCbox.Location = new System.Drawing.Point(6, 22);
this.allowLibationFixupCbox.Name = "allowLibationFixupCbox";
this.allowLibationFixupCbox.Size = new System.Drawing.Size(262, 19);
this.allowLibationFixupCbox.TabIndex = 8;
this.allowLibationFixupCbox.TabIndex = 10;
this.allowLibationFixupCbox.Text = "Allow Libation to fix up audiobook metadata";
this.allowLibationFixupCbox.UseVisualStyleBackColor = true;
this.allowLibationFixupCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
@@ -202,10 +238,10 @@
// convertLossyRb
//
this.convertLossyRb.AutoSize = true;
this.convertLossyRb.Location = new System.Drawing.Point(6, 81);
this.convertLossyRb.Location = new System.Drawing.Point(6, 101);
this.convertLossyRb.Name = "convertLossyRb";
this.convertLossyRb.Size = new System.Drawing.Size(329, 19);
this.convertLossyRb.TabIndex = 10;
this.convertLossyRb.TabIndex = 12;
this.convertLossyRb.Text = "Download my books as .MP3 files (transcode if necessary)";
this.convertLossyRb.UseVisualStyleBackColor = true;
//
@@ -213,10 +249,10 @@
//
this.convertLosslessRb.AutoSize = true;
this.convertLosslessRb.Checked = true;
this.convertLosslessRb.Location = new System.Drawing.Point(6, 56);
this.convertLosslessRb.Location = new System.Drawing.Point(6, 76);
this.convertLosslessRb.Name = "convertLosslessRb";
this.convertLosslessRb.Size = new System.Drawing.Size(335, 19);
this.convertLosslessRb.TabIndex = 9;
this.convertLosslessRb.TabIndex = 11;
this.convertLosslessRb.TabStop = true;
this.convertLosslessRb.Text = "Download my books in the original audio format (Lossless)";
this.convertLosslessRb.UseVisualStyleBackColor = true;
@@ -225,10 +261,11 @@
//
this.inProgressSelectControl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.inProgressSelectControl.Location = new System.Drawing.Point(7, 197);
this.inProgressSelectControl.Location = new System.Drawing.Point(7, 247);
this.inProgressSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.inProgressSelectControl.Name = "inProgressSelectControl";
this.inProgressSelectControl.Size = new System.Drawing.Size(552, 52);
this.inProgressSelectControl.TabIndex = 16;
this.inProgressSelectControl.Size = new System.Drawing.Size(894, 52);
this.inProgressSelectControl.TabIndex = 19;
//
// logsBtn
//
@@ -245,6 +282,7 @@
this.booksSelectControl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.booksSelectControl.Location = new System.Drawing.Point(7, 37);
this.booksSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.booksSelectControl.Name = "booksSelectControl";
this.booksSelectControl.Size = new System.Drawing.Size(895, 87);
this.booksSelectControl.TabIndex = 2;
@@ -286,7 +324,7 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.cancelBtn;
this.ClientSize = new System.Drawing.Size(933, 488);
this.ClientSize = new System.Drawing.Size(933, 539);
this.Controls.Add(this.logsBtn);
this.Controls.Add(this.loggingLevelCb);
this.Controls.Add(this.loggingLevelLbl);
@@ -334,5 +372,8 @@
private System.Windows.Forms.RadioButton badBookAbortRb;
private System.Windows.Forms.RadioButton badBookAskRb;
private System.Windows.Forms.RadioButton badBookIgnoreRb;
}
private System.Windows.Forms.CheckBox downloadEpisodesCb;
private System.Windows.Forms.CheckBox importEpisodesCb;
private System.Windows.Forms.CheckBox splitFilesByChapterCbox;
}
}

View File

@@ -26,6 +26,8 @@ namespace LibationWinForms.Dialogs
loggingLevelCb.SelectedItem = config.LogLevel;
}
this.importEpisodesCb.Text = desc(nameof(config.ImportEpisodes));
this.downloadEpisodesCb.Text = desc(nameof(config.DownloadEpisodes));
this.booksLocationDescLbl.Text = desc(nameof(config.Books));
this.inProgressDescLbl.Text = desc(nameof(config.InProgress));
@@ -41,9 +43,12 @@ namespace LibationWinForms.Dialogs
"Books");
booksSelectControl.SelectDirectory(config.Books);
importEpisodesCb.Checked = config.ImportEpisodes;
downloadEpisodesCb.Checked = config.DownloadEpisodes;
allowLibationFixupCbox.Checked = config.AllowLibationFixup;
convertLosslessRb.Checked = !config.DecryptToLossy;
convertLossyRb.Checked = config.DecryptToLossy;
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
allowLibationFixupCbox_CheckedChanged(this, e);
@@ -121,8 +126,11 @@ namespace LibationWinForms.Dialogs
MessageBoxVerboseLoggingWarning.ShowIfTrue();
}
config.ImportEpisodes = importEpisodesCb.Checked;
config.DownloadEpisodes = downloadEpisodesCb.Checked;
config.AllowLibationFixup = allowLibationFixupCbox.Checked;
config.DecryptToLossy = convertLossyRb.Checked;
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
config.InProgress = inProgressSelectControl.SelectedDirectory;

View File

@@ -42,10 +42,11 @@ namespace LibationWinForms
// Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration
var config = AppScaffolding.LibationScaffolding.RunPreConfigMigrations();
// do this as soon as possible (post-config)
RunInstaller(config);
// most migrations go in here
AppScaffolding.LibationScaffolding.RunPostConfigMigrations();
AppScaffolding.LibationScaffolding.RunPostConfigMigrations(config);
// migrations which require Forms or are long-running
RunWindowsOnlyMigrations(config);
@@ -53,9 +54,10 @@ namespace LibationWinForms
MessageBoxVerboseLoggingWarning.ShowIfTrue();
#if !DEBUG
checkForUpdate();
checkForUpdate();
#endif
AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config);
}
catch (Exception ex)
{
@@ -72,8 +74,6 @@ namespace LibationWinForms
return;
}
AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding();
Application.Run(new Form1());
}
@@ -143,8 +143,6 @@ namespace LibationWinForms
// if 'new user' was clicked, or if 'returning user' chose new install: show basic settings dialog
config.Books ??= Path.Combine(defaultLibationFilesDir, "Books");
config.InProgress ??= Configuration.WinTemp;
config.AllowLibationFixup = true;
config.DecryptToLossy = false;
if (new SettingsDialog().ShowDialog() != DialogResult.OK)
{
@@ -158,6 +156,7 @@ namespace LibationWinForms
CancelInstallation();
}
/// <summary>migrations which require Forms or are long-running</summary>
private static void RunWindowsOnlyMigrations(Configuration config)
{
// only supported in winforms. don't move to app scaffolding
@@ -170,9 +169,6 @@ namespace LibationWinForms
#region migrate to v5.0.0 re-register device if device info not in settings
private static void migrate_to_v5_0_0(Configuration config)
{
if (!config.Exists(nameof(config.AllowLibationFixup)))
config.AllowLibationFixup = true;
if (!File.Exists(AudibleApiStorage.AccountsSettingsFile))
return;