From bba9c2ba7b3f8e607de628d761f26bffa0b4be2a Mon Sep 17 00:00:00 2001 From: Mbucari Date: Fri, 10 Feb 2023 09:35:21 -0700 Subject: [PATCH] Add Locate Audiobooks function (#485) --- .../LiberatedStatusBatchManualDialog.axaml | 2 +- .../Dialogs/LocateAudiobooksDialog.axaml | 30 +++++ .../Dialogs/LocateAudiobooksDialog.axaml.cs | 115 ++++++++++++++++++ .../Dialogs/MessageBoxWindow.axaml | 6 +- Source/LibationAvalonia/MessageBox.cs | 1 + .../Views/MainWindow.Export.cs | 17 +-- .../Views/MainWindow.ScanManual.cs | 7 ++ .../LibationAvalonia/Views/MainWindow.axaml | 3 + .../LibationFileManager/AudioFileLocator.cs | 60 +++++++++ Source/LibationFileManager/FilePathCache.cs | 6 +- .../LocateAudiobooksDialog.Designer.cs | 107 ++++++++++++++++ .../Dialogs/LocateAudiobooksDialog.cs | 98 +++++++++++++++ .../Dialogs/LocateAudiobooksDialog.resx | 60 +++++++++ Source/LibationWinForms/Form1.Designer.cs | 32 +++-- Source/LibationWinForms/Form1.ScanManual.cs | 5 + Source/LibationWinForms/Form1.cs | 1 + 16 files changed, 530 insertions(+), 20 deletions(-) create mode 100644 Source/LibationAvalonia/Dialogs/LocateAudiobooksDialog.axaml create mode 100644 Source/LibationAvalonia/Dialogs/LocateAudiobooksDialog.axaml.cs create mode 100644 Source/LibationFileManager/AudioFileLocator.cs create mode 100644 Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.Designer.cs create mode 100644 Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.cs create mode 100644 Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.resx diff --git a/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml b/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml index bdb39d0c..9aa9cb95 100644 --- a/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml @@ -12,7 +12,7 @@ Icon="/Assets/libation.ico"> - + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/LibationAvalonia/Dialogs/LocateAudiobooksDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/LocateAudiobooksDialog.axaml.cs new file mode 100644 index 00000000..699d6384 --- /dev/null +++ b/Source/LibationAvalonia/Dialogs/LocateAudiobooksDialog.axaml.cs @@ -0,0 +1,115 @@ +using ApplicationServices; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Platform.Storage; +using Avalonia.Platform.Storage.FileIO; +using DataLayer; +using LibationAvalonia.ViewModels; +using LibationFileManager; +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace LibationAvalonia.Dialogs +{ + public partial class LocateAudiobooksDialog : DialogWindow + { + private event EventHandler FileFound; + private readonly CancellationTokenSource tokenSource = new(); + private readonly List foundAsins = new(); + private readonly LocatedAudiobooksViewModel _viewModel; + public LocateAudiobooksDialog() + { + InitializeComponent(); + + DataContext = _viewModel = new(); + this.RestoreSizeAndLocation(Configuration.Instance); + + if (Design.IsDesignMode) + { + _viewModel.FoundFiles.Add(new("[0000001]", "Filename 1.m4b")); + _viewModel.FoundFiles.Add(new("[0000002]", "Filename 2.m4b")); + } + else + { + Opened += LocateAudiobooksDialog_Opened; + FileFound += LocateAudiobooks_FileFound; + Closing += LocateAudiobooksDialog_Closing; + } + } + + private void LocateAudiobooksDialog_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + tokenSource.Cancel(); + //If this dialog is closed before it's completed, Closing is fired + //once for the form clising and again for the MessageBox closing. + Closing -= LocateAudiobooksDialog_Closing; + this.SaveSizeAndLocation(Configuration.Instance); + } + + private void LocateAudiobooks_FileFound(object sender, FilePathCache.CacheEntry e) + { + var newItem = new Tuple($"[{e.Id}]", Path.GetFileName(e.Path)); + _viewModel.FoundFiles.Add(newItem); + foundAudiobooksLB.SelectedItem = newItem; + + if (!foundAsins.Any(asin => asin == e.Id)) + { + foundAsins.Add(e.Id); + _viewModel.FoundAsins = foundAsins.Count; + } + } + + private async void LocateAudiobooksDialog_Opened(object sender, EventArgs e) + { + var folderPicker = new FolderPickerOpenOptions + { + Title = "Select the folder to search for audiobooks", + AllowMultiple = false, + SuggestedStartLocation = new BclStorageFolder(Configuration.Instance.Books.PathWithoutPrefix) + }; + + var selectedFolder = await StorageProvider.OpenFolderPickerAsync(folderPicker); + + if (selectedFolder.FirstOrDefault().TryGetUri(out var uri) is not true || !Directory.Exists(uri.LocalPath)) + { + await CancelAndCloseAsync(); + return; + } + + using var context = DbContexts.GetContext(); + + await foreach (var book in AudioFileLocator.FindAudiobooks(uri.LocalPath, tokenSource.Token)) + { + try + { + FilePathCache.Insert(book); + + var lb = context.GetLibraryBook_Flat_NoTracking(book.Id); + if (lb.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Liberated) + await Task.Run(() => lb.UpdateBookStatus(LiberatedStatus.Liberated)); + + FileFound?.Invoke(this, book); + } + catch (Exception ex) + { + Serilog.Log.Error(ex, "Error adding found audiobook file to Libation. {@audioFile}", book); + } + } + + await MessageBox.Show(this, $"Libation has found {foundAsins.Count} unique audiobooks and added them to its database. ", $"Found {foundAsins.Count} Audiobooks"); + await SaveAndCloseAsync(); + } + } + + public class LocatedAudiobooksViewModel : ViewModelBase + { + private int _foundAsins = 0; + public AvaloniaList> FoundFiles { get; } = new(); + public int FoundAsins { get => _foundAsins; set => this.RaiseAndSetIfChanged(ref _foundAsins, value); } + } +} diff --git a/Source/LibationAvalonia/Dialogs/MessageBoxWindow.axaml b/Source/LibationAvalonia/Dialogs/MessageBoxWindow.axaml index a911a335..07a617f9 100644 --- a/Source/LibationAvalonia/Dialogs/MessageBoxWindow.axaml +++ b/Source/LibationAvalonia/Dialogs/MessageBoxWindow.axaml @@ -35,13 +35,13 @@ diff --git a/Source/LibationAvalonia/MessageBox.cs b/Source/LibationAvalonia/MessageBox.cs index dee03e8d..a1ee276f 100644 --- a/Source/LibationAvalonia/MessageBox.cs +++ b/Source/LibationAvalonia/MessageBox.cs @@ -154,6 +154,7 @@ Libation. private static async Task ShowCoreAsync(Window owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, bool saveAndRestorePosition = true) { + owner = owner?.IsLoaded is true ? owner : null; var dialog = await Dispatcher.UIThread.InvokeAsync(() => CreateMessageBox(owner, message, caption, buttons, icon, defaultButton, saveAndRestorePosition)); return await DisplayWindow(dialog, owner); diff --git a/Source/LibationAvalonia/Views/MainWindow.Export.cs b/Source/LibationAvalonia/Views/MainWindow.Export.cs index 24407f90..6759b09d 100644 --- a/Source/LibationAvalonia/Views/MainWindow.Export.cs +++ b/Source/LibationAvalonia/Views/MainWindow.Export.cs @@ -1,6 +1,7 @@ using ApplicationServices; using Avalonia.Controls; using Avalonia.Platform.Storage; +using FileManager; using LibationFileManager; using System; using System.Linq; @@ -20,14 +21,14 @@ namespace LibationAvalonia.Views { Title = "Where to export Library", SuggestedStartLocation = new Avalonia.Platform.Storage.FileIO.BclStorageFolder(Configuration.Instance.Books.PathWithoutPrefix), - SuggestedFileName = $"Libation Library Export {DateTime.Now:yyyy-MM-dd}.xlsx", + SuggestedFileName = $"Libation Library Export {DateTime.Now:yyyy-MM-dd}", DefaultExtension = "xlsx", ShowOverwritePrompt = true, FileTypeChoices = new FilePickerFileType[] { - new("Excel Workbook (*.xlsx)") { Patterns = new[] { "xlsx" } }, - new("CSV files (*.csv)") { Patterns = new[] { "csv" } }, - new("JSON files (*.json)") { Patterns = new[] { "json" } }, + new("Excel Workbook (*.xlsx)") { Patterns = new[] { "*.xlsx" } }, + new("CSV files (*.csv)") { Patterns = new[] { "*.csv" } }, + new("JSON files (*.json)") { Patterns = new[] { "*.json" } }, new("All files (*.*)") { Patterns = new[] { "*" } }, } }; @@ -36,17 +37,17 @@ namespace LibationAvalonia.Views if (selectedFile?.TryGetUri(out var uri) is not true) return; - var ext = System.IO.Path.GetExtension(uri.LocalPath); + var ext = FileUtility.GetStandardizedExtension(System.IO.Path.GetExtension(uri.LocalPath)); switch (ext) { - case "xlsx": // xlsx + case ".xlsx": // xlsx default: LibraryExporter.ToXlsx(uri.LocalPath); break; - case "csv": // csv + case ".csv": // csv LibraryExporter.ToCsv(uri.LocalPath); break; - case "json": // json + case ".json": // json LibraryExporter.ToJson(uri.LocalPath); break; } diff --git a/Source/LibationAvalonia/Views/MainWindow.ScanManual.cs b/Source/LibationAvalonia/Views/MainWindow.ScanManual.cs index acd9602e..a94984c4 100644 --- a/Source/LibationAvalonia/Views/MainWindow.ScanManual.cs +++ b/Source/LibationAvalonia/Views/MainWindow.ScanManual.cs @@ -1,6 +1,7 @@ using ApplicationServices; using AudibleUtilities; using Avalonia.Controls; +using LibationAvalonia.Dialogs; using LibationFileManager; using System; using System.Collections.Generic; @@ -77,5 +78,11 @@ namespace LibationAvalonia.Views ex); } } + + private async void locateAudiobooksToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + var locateDialog = new LocateAudiobooksDialog(); + await locateDialog.ShowDialog(this); + } } } diff --git a/Source/LibationAvalonia/Views/MainWindow.axaml b/Source/LibationAvalonia/Views/MainWindow.axaml index 12445243..6ec2e945 100644 --- a/Source/LibationAvalonia/Views/MainWindow.axaml +++ b/Source/LibationAvalonia/Views/MainWindow.axaml @@ -51,6 +51,9 @@ + + + diff --git a/Source/LibationFileManager/AudioFileLocator.cs b/Source/LibationFileManager/AudioFileLocator.cs new file mode 100644 index 00000000..8413de6c --- /dev/null +++ b/Source/LibationFileManager/AudioFileLocator.cs @@ -0,0 +1,60 @@ +using AAXClean; +using Dinah.Core; +using FileManager; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace LibationFileManager +{ + public static class AudioFileLocator + { + private static EnumerationOptions enumerationOptions { get; } = new() + { + RecurseSubdirectories = true, + IgnoreInaccessible = true, + MatchCasing = MatchCasing.CaseInsensitive + }; + + public static async IAsyncEnumerable FindAudiobooks(LongPath searchDirectory, [EnumeratorCancellation] CancellationToken cancellationToken) + { + ArgumentValidator.EnsureNotNull(searchDirectory, nameof(searchDirectory)); + + foreach (LongPath path in Directory.EnumerateFiles(searchDirectory, "*.M4B", enumerationOptions)) + { + if (cancellationToken.IsCancellationRequested) + yield break; + + int generation = 0; + FilePathCache.CacheEntry audioFile = default; + + try + { + using var fileStream = File.OpenRead(path); + + var mp4File = await Task.Run(() => new Mp4File(fileStream), cancellationToken); + + generation = GC.GetGeneration(mp4File); + + if (mp4File?.AppleTags?.Asin is not null) + audioFile = new FilePathCache.CacheEntry(mp4File.AppleTags.Asin, FileType.Audio, path); + + } + catch(Exception ex) + { + Serilog.Log.Error(ex, "Error checking for asin in {@file}", path); + } + finally + { + GC.Collect(generation); + } + + if (audioFile is not null) + yield return audioFile; + } + } + } +} diff --git a/Source/LibationFileManager/FilePathCache.cs b/Source/LibationFileManager/FilePathCache.cs index 56eddddb..78cd990e 100644 --- a/Source/LibationFileManager/FilePathCache.cs +++ b/Source/LibationFileManager/FilePathCache.cs @@ -86,7 +86,11 @@ namespace LibationFileManager public static void Insert(string id, string path) { var type = FileTypes.GetFileTypeFromPath(path); - var entry = new CacheEntry(id, type, path); + Insert(new CacheEntry(id, type, path)); + } + + public static void Insert(CacheEntry entry) + { cache.Add(entry); Inserted?.Invoke(null, entry); save(); diff --git a/Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.Designer.cs b/Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.Designer.cs new file mode 100644 index 00000000..2bb3f854 --- /dev/null +++ b/Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.Designer.cs @@ -0,0 +1,107 @@ +namespace LibationWinForms.Dialogs +{ + partial class LocateAudiobooksDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.foundAudiobooksLV = new System.Windows.Forms.ListView(); + this.columnHeader1 = new System.Windows.Forms.ColumnHeader(); + this.columnHeader2 = new System.Windows.Forms.ColumnHeader(); + this.booksFoundLbl = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(108, 15); + this.label1.TabIndex = 1; + this.label1.Text = "Found Audiobooks"; + // + // foundAudiobooksLV + // + this.foundAudiobooksLV.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.foundAudiobooksLV.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnHeader1, + this.columnHeader2}); + this.foundAudiobooksLV.FullRowSelect = true; + this.foundAudiobooksLV.Location = new System.Drawing.Point(12, 33); + this.foundAudiobooksLV.Name = "foundAudiobooksLV"; + this.foundAudiobooksLV.Size = new System.Drawing.Size(321, 261); + this.foundAudiobooksLV.TabIndex = 2; + this.foundAudiobooksLV.UseCompatibleStateImageBehavior = false; + this.foundAudiobooksLV.View = System.Windows.Forms.View.Details; + // + // columnHeader1 + // + this.columnHeader1.Text = "Book ID"; + this.columnHeader1.Width = 85; + // + // columnHeader2 + // + this.columnHeader2.Text = "Title"; + // + // booksFoundLbl + // + this.booksFoundLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.booksFoundLbl.AutoSize = true; + this.booksFoundLbl.Location = new System.Drawing.Point(253, 9); + this.booksFoundLbl.Name = "booksFoundLbl"; + this.booksFoundLbl.Size = new System.Drawing.Size(80, 15); + this.booksFoundLbl.TabIndex = 3; + this.booksFoundLbl.Text = "IDs Found: {0}"; + this.booksFoundLbl.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // LocateAudiobooksDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(345, 306); + this.Controls.Add(this.booksFoundLbl); + this.Controls.Add(this.foundAudiobooksLV); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; + this.Name = "LocateAudiobooksDialog"; + this.Text = "Locate Audiobooks"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.ListView foundAudiobooksLV; + private System.Windows.Forms.ColumnHeader columnHeader1; + private System.Windows.Forms.ColumnHeader columnHeader2; + private System.Windows.Forms.Label booksFoundLbl; + } +} \ No newline at end of file diff --git a/Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.cs b/Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.cs new file mode 100644 index 00000000..5df7b0fc --- /dev/null +++ b/Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.cs @@ -0,0 +1,98 @@ +using ApplicationServices; +using DataLayer; +using LibationFileManager; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace LibationWinForms.Dialogs +{ + public partial class LocateAudiobooksDialog : Form + { + private event EventHandler FileFound; + private readonly CancellationTokenSource tokenSource = new(); + private readonly List foundAsins = new(); + private readonly string labelFormatText; + public LocateAudiobooksDialog() + { + InitializeComponent(); + + labelFormatText = booksFoundLbl.Text; + setFoundBookCount(0); + + this.SetLibationIcon(); + this.RestoreSizeAndLocation(Configuration.Instance); + + Shown += LocateAudiobooks_Shown; + FileFound += LocateAudiobooks_FileFound; + FormClosing += LocateAudiobooks_FormClosing; + } + + private void setFoundBookCount(int count) + => booksFoundLbl.Text = string.Format(labelFormatText, count); + + private void LocateAudiobooks_FileFound(object sender, FilePathCache.CacheEntry e) + { + foundAudiobooksLV.Items + .Add(new ListViewItem(new string[] { $"[{e.Id}]", Path.GetFileName(e.Path) })) + .EnsureVisible(); + + foundAudiobooksLV.AutoResizeColumn(1, ColumnHeaderAutoResizeStyle.ColumnContent); + + if (!foundAsins.Any(asin => asin == e.Id)) + { + foundAsins.Add(e.Id); + setFoundBookCount(foundAsins.Count); + } + } + + private void LocateAudiobooks_FormClosing(object sender, FormClosingEventArgs e) + { + tokenSource.Cancel(); + this.SaveSizeAndLocation(Configuration.Instance); + } + + private async void LocateAudiobooks_Shown(object sender, EventArgs e) + { + var fbd = new FolderBrowserDialog + { + Description = "Select the folder to search for audiobooks", + UseDescriptionForTitle = true, + InitialDirectory = Configuration.Instance.Books + }; + + if (fbd.ShowDialog() != DialogResult.OK || !Directory.Exists(fbd.SelectedPath)) + { + Close(); + return; + } + + using var context = DbContexts.GetContext(); + + await foreach (var book in AudioFileLocator.FindAudiobooks(fbd.SelectedPath, tokenSource.Token)) + { + try + { + FilePathCache.Insert(book); + + var lb = context.GetLibraryBook_Flat_NoTracking(book.Id); + if (lb.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Liberated) + await Task.Run(() => lb.UpdateBookStatus(LiberatedStatus.Liberated)); + + this.Invoke(FileFound, this, book); + } + catch(Exception ex) + { + Serilog.Log.Error(ex, "Error adding found audiobook file to Libation. {@audioFile}", book); + } + } + + MessageBox.Show(this, $"Libation has found {foundAsins.Count} unique audiobooks and added them to its database. ", $"Found {foundAsins.Count} Audiobooks"); + Close(); + } + } +} diff --git a/Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.resx b/Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.resx new file mode 100644 index 00000000..f298a7be --- /dev/null +++ b/Source/LibationWinForms/Dialogs/LocateAudiobooksDialog.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs index 335d47f4..d60e3e3f 100644 --- a/Source/LibationWinForms/Form1.Designer.cs +++ b/Source/LibationWinForms/Form1.Designer.cs @@ -60,8 +60,10 @@ this.setBookDownloadedManualToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.setPdfDownloadedManualToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.setDownloadedAutoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.locateAudiobooksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.accountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.basicSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); @@ -149,7 +151,9 @@ this.scanLibraryToolStripMenuItem, this.scanLibraryOfAllAccountsToolStripMenuItem, this.scanLibraryOfSomeAccountsToolStripMenuItem, - this.removeLibraryBooksToolStripMenuItem}); + this.removeLibraryBooksToolStripMenuItem, + this.toolStripSeparator3, + this.locateAudiobooksToolStripMenuItem}); this.importToolStripMenuItem.Name = "importToolStripMenuItem"; this.importToolStripMenuItem.Size = new System.Drawing.Size(55, 20); this.importToolStripMenuItem.Text = "&Import"; @@ -560,10 +564,22 @@ this.processBookQueue1.Name = "processBookQueue1"; this.processBookQueue1.Size = new System.Drawing.Size(430, 640); this.processBookQueue1.TabIndex = 0; - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + // + // locateAudiobooksToolStripMenuItem + // + this.locateAudiobooksToolStripMenuItem.Name = "locateAudiobooksToolStripMenuItem"; + this.locateAudiobooksToolStripMenuItem.Size = new System.Drawing.Size(247, 22); + this.locateAudiobooksToolStripMenuItem.Text = "Locate Audiobooks"; + this.locateAudiobooksToolStripMenuItem.Click += new System.EventHandler(this.locateAudiobooksToolStripMenuItem_Click); + // + // toolStripSeparator3 + // + this.toolStripSeparator3.Name = "toolStripSeparator3"; + this.toolStripSeparator3.Size = new System.Drawing.Size(244, 6); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1463, 640); this.Controls.Add(this.splitContainer1); @@ -630,6 +646,8 @@ private System.Windows.Forms.ToolStripMenuItem setBookDownloadedManualToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem setDownloadedAutoToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + private System.Windows.Forms.ToolStripMenuItem locateAudiobooksToolStripMenuItem; private LibationWinForms.FormattableToolStripMenuItem liberateVisibleToolStripMenuItem_LiberateMenu; private System.Windows.Forms.SplitContainer splitContainer1; private LibationWinForms.ProcessQueue.ProcessQueueControl processBookQueue1; diff --git a/Source/LibationWinForms/Form1.ScanManual.cs b/Source/LibationWinForms/Form1.ScanManual.cs index b90c8826..b238cbd6 100644 --- a/Source/LibationWinForms/Form1.ScanManual.cs +++ b/Source/LibationWinForms/Form1.ScanManual.cs @@ -89,5 +89,10 @@ namespace LibationWinForms ex); } } + + private void locateAudiobooksToolStripMenuItem_Click(object sender, EventArgs e) + { + new LocateAudiobooksDialog().ShowDialog(); + } } } diff --git a/Source/LibationWinForms/Form1.cs b/Source/LibationWinForms/Form1.cs index 8b7c6767..3e450559 100644 --- a/Source/LibationWinForms/Form1.cs +++ b/Source/LibationWinForms/Form1.cs @@ -7,6 +7,7 @@ using ApplicationServices; using Dinah.Core; using Dinah.Core.Threading; using LibationFileManager; +using LibationWinForms.Dialogs; namespace LibationWinForms {