diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs index 4843ecd1..f8e290de 100644 --- a/FileLiberator/DownloadDecryptBook.cs +++ b/FileLiberator/DownloadDecryptBook.cs @@ -155,7 +155,7 @@ namespace FileLiberator var dest = AudibleFileStorage.Audio.IsFileTypeMatch(f) ? audioFileName - // non-audio filename: safetitle_limit50char + " [" + productId + "][" + audio_ext +"]." + non_audio_ext + // 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") diff --git a/FileLiberator/DownloadPdf.cs b/FileLiberator/DownloadPdf.cs index 0ed4cd6b..5295b3aa 100644 --- a/FileLiberator/DownloadPdf.cs +++ b/FileLiberator/DownloadPdf.cs @@ -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 Begin; + public event EventHandler Completed; + + public event EventHandler StreamingBegin; + public event EventHandler StreamingProgressChanged; + public event EventHandler StreamingCompleted; + + public event EventHandler StatusUpdate; + public event EventHandler StreamingTimeRemaining; + + public bool Validate(LibraryBook libraryBook) => !string.IsNullOrWhiteSpace(getdownloadUrl(libraryBook)) && !libraryBook.Book.PDF_Exists; - public override async Task ProcessItemAsync(LibraryBook libraryBook) + public async Task 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 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(); + 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) diff --git a/FileLiberator/DownloadableBase.cs b/FileLiberator/DownloadableBase.cs deleted file mode 100644 index 5ac6652e..00000000 --- a/FileLiberator/DownloadableBase.cs +++ /dev/null @@ -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 Begin; - public event EventHandler Completed; - - public event EventHandler StreamingBegin; - public event EventHandler StreamingProgressChanged; - public event EventHandler StreamingCompleted; - - public event EventHandler StatusUpdate; - public event EventHandler StreamingTimeRemaining; - - protected void Invoke_StatusUpdate(string message) => StatusUpdate?.Invoke(this, message); - - public abstract bool Validate(LibraryBook libraryBook); - - public abstract Task ProcessItemAsync(LibraryBook libraryBook); - - // do NOT use ConfigureAwait(false) on ProcessAsync() - // often calls events which prints to forms in the UI context - public async Task ProcessAsync(LibraryBook libraryBook) - { - Begin?.Invoke(this, libraryBook); - - try - { - return await ProcessItemAsync(libraryBook); - } - finally - { - Completed?.Invoke(this, libraryBook); - } - } - - protected async Task PerformDownloadAsync(string proposedDownloadFilePath, Func, Task> func) - { - var progress = new Progress(); - 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); - } - } - } -} diff --git a/FileLiberator/IProcessableExt.cs b/FileLiberator/IProcessableExt.cs index 0ba57965..0e06618c 100644 --- a/FileLiberator/IProcessableExt.cs +++ b/FileLiberator/IProcessableExt.cs @@ -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 GetValidLibraryBooks(this IProcessable processable, IEnumerable 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 ProcessSingleAsync(this IProcessable processable, LibraryBook libraryBook, bool validate) { diff --git a/FileManager/AudibleFileStorage.cs b/FileManager/AudibleFileStorage.cs index 362e1371..30d859d5 100644 --- a/FileManager/AudibleFileStorage.cs +++ b/FileManager/AudibleFileStorage.cs @@ -48,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); } @@ -59,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) @@ -76,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) diff --git a/FileManager/BackgroundFileSystem.cs b/FileManager/BackgroundFileSystem.cs index 0546ac07..f999d4cb 100644 --- a/FileManager/BackgroundFileSystem.cs +++ b/FileManager/BackgroundFileSystem.cs @@ -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 @@ -38,10 +36,10 @@ namespace FileManager Init(); } - public string FindFile(string regexPattern, RegexOptions options) + public string FindFile(System.Text.RegularExpressions.Regex regex) { lock (fsCacheLocker) - return fsCache.FirstOrDefault(s => Regex.IsMatch(s, regexPattern, options)); + return fsCache.FirstOrDefault(s => regex.IsMatch(s)); } public void RefreshFiles() @@ -61,13 +59,15 @@ namespace FileManager fsCache.AddRange(Directory.EnumerateFiles(RootDirectory, SearchPattern, SearchOption)); directoryChangesEvents = new BlockingCollection(); - 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(); diff --git a/FileManager/Configuration.cs b/FileManager/Configuration.cs index 16930238..69cd65b5 100644 --- a/FileManager/Configuration.cs +++ b/FileManager/Configuration.cs @@ -125,6 +125,13 @@ namespace FileManager set => persistentDictionary.SetString(nameof(BadBook), value.ToString()); } + [Description("Download episodes? (eg: podcasts)")] + public bool DownloadEpisodes + { + get => persistentDictionary.GetNonString(nameof(DownloadEpisodes)); + set => persistentDictionary.SetNonString(nameof(DownloadEpisodes), value); + } + #endregion #region known directories diff --git a/FileManager/FileUtility.cs b/FileManager/FileUtility.cs index 53bd9e7b..22bed934 100644 --- a/FileManager/FileUtility.cs +++ b/FileManager/FileUtility.cs @@ -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 ""; diff --git a/LibationCli/Setup.cs b/LibationCli/Setup.cs index 30de7af2..2ffc835b 100644 --- a/LibationCli/Setup.cs +++ b/LibationCli/Setup.cs @@ -24,8 +24,8 @@ namespace LibationCli var config = LibationScaffolding.RunPreConfigMigrations(); - LibationScaffolding.RunPostConfigMigrations(); - LibationScaffolding.RunPostMigrationScaffolding(); + LibationScaffolding.RunPostConfigMigrations(config); + LibationScaffolding.RunPostMigrationScaffolding(config); #if !DEBUG checkForUpdate(); diff --git a/LibationWinForms/Dialogs/SettingsDialog.Designer.cs b/LibationWinForms/Dialogs/SettingsDialog.Designer.cs index bb9694f3..854dd3a2 100644 --- a/LibationWinForms/Dialogs/SettingsDialog.Designer.cs +++ b/LibationWinForms/Dialogs/SettingsDialog.Designer.cs @@ -48,6 +48,7 @@ this.booksGb = new System.Windows.Forms.GroupBox(); this.loggingLevelLbl = new System.Windows.Forms.Label(); this.loggingLevelCb = new System.Windows.Forms.ComboBox(); + this.downloadEpisodesCb = new System.Windows.Forms.CheckBox(); this.advancedSettingsGb.SuspendLayout(); this.badBookGb.SuspendLayout(); this.decryptAndConvertGb.SuspendLayout(); @@ -67,7 +68,7 @@ // inProgressDescLbl // this.inProgressDescLbl.AutoSize = true; - this.inProgressDescLbl.Location = new System.Drawing.Point(8, 149); + this.inProgressDescLbl.Location = new System.Drawing.Point(8, 174); 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); @@ -77,7 +78,7 @@ // 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, 470); 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); @@ -90,7 +91,7 @@ // 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, 470); 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); @@ -104,6 +105,7 @@ 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.downloadEpisodesCb); this.advancedSettingsGb.Controls.Add(this.badBookGb); this.advancedSettingsGb.Controls.Add(this.decryptAndConvertGb); this.advancedSettingsGb.Controls.Add(this.inProgressSelectControl); @@ -112,7 +114,7 @@ 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, 283); this.advancedSettingsGb.TabIndex = 6; this.advancedSettingsGb.TabStop = false; this.advancedSettingsGb.Text = "Advanced settings for control freaks"; @@ -123,7 +125,7 @@ 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, 47); this.badBookGb.Name = "badBookGb"; this.badBookGb.Size = new System.Drawing.Size(529, 124); this.badBookGb.TabIndex = 11; @@ -179,7 +181,7 @@ 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(8, 47); this.decryptAndConvertGb.Name = "decryptAndConvertGb"; this.decryptAndConvertGb.Size = new System.Drawing.Size(359, 124); this.decryptAndConvertGb.TabIndex = 7; @@ -225,7 +227,7 @@ // 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, 222); this.inProgressSelectControl.Name = "inProgressSelectControl"; this.inProgressSelectControl.Size = new System.Drawing.Size(552, 52); this.inProgressSelectControl.TabIndex = 16; @@ -280,13 +282,23 @@ this.loggingLevelCb.Size = new System.Drawing.Size(129, 23); this.loggingLevelCb.TabIndex = 4; // + // downloadEpisodesCb + // + this.downloadEpisodesCb.AutoSize = true; + this.downloadEpisodesCb.Location = new System.Drawing.Point(8, 22); + this.downloadEpisodesCb.Name = "downloadEpisodesCb"; + this.downloadEpisodesCb.Size = new System.Drawing.Size(163, 19); + this.downloadEpisodesCb.TabIndex = 17; + this.downloadEpisodesCb.Text = "[download episodes desc]"; + this.downloadEpisodesCb.UseVisualStyleBackColor = true; + // // SettingsDialog // this.AcceptButton = this.saveBtn; 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, 513); this.Controls.Add(this.logsBtn); this.Controls.Add(this.loggingLevelCb); this.Controls.Add(this.loggingLevelLbl); @@ -334,5 +346,6 @@ private System.Windows.Forms.RadioButton badBookAbortRb; private System.Windows.Forms.RadioButton badBookAskRb; private System.Windows.Forms.RadioButton badBookIgnoreRb; + private System.Windows.Forms.CheckBox downloadEpisodesCb; } } \ No newline at end of file diff --git a/LibationWinForms/Dialogs/SettingsDialog.cs b/LibationWinForms/Dialogs/SettingsDialog.cs index cca90cbe..3e234c43 100644 --- a/LibationWinForms/Dialogs/SettingsDialog.cs +++ b/LibationWinForms/Dialogs/SettingsDialog.cs @@ -26,6 +26,7 @@ namespace LibationWinForms.Dialogs loggingLevelCb.SelectedItem = config.LogLevel; } + this.downloadEpisodesCb.Text = desc(nameof(config.DownloadEpisodes)); this.booksLocationDescLbl.Text = desc(nameof(config.Books)); this.inProgressDescLbl.Text = desc(nameof(config.InProgress)); @@ -41,6 +42,7 @@ namespace LibationWinForms.Dialogs "Books"); booksSelectControl.SelectDirectory(config.Books); + downloadEpisodesCb.Checked = config.DownloadEpisodes; allowLibationFixupCbox.Checked = config.AllowLibationFixup; convertLosslessRb.Checked = !config.DecryptToLossy; convertLossyRb.Checked = config.DecryptToLossy; @@ -121,6 +123,7 @@ namespace LibationWinForms.Dialogs MessageBoxVerboseLoggingWarning.ShowIfTrue(); } + config.DownloadEpisodes = downloadEpisodesCb.Checked; config.AllowLibationFixup = allowLibationFixupCbox.Checked; config.DecryptToLossy = convertLossyRb.Checked; diff --git a/LibationWinForms/Program.cs b/LibationWinForms/Program.cs index b8b25c21..749a10f1 100644 --- a/LibationWinForms/Program.cs +++ b/LibationWinForms/Program.cs @@ -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(); } + /// migrations which require Forms or are long-running 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;