From 255c0a33598cbd6fa0d476c81c2556f2d2ea2c1b Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 19:58:59 -0600 Subject: [PATCH 01/22] Move filtering into SyncBindingSource --- Source/LibationWinForms/SyncBindingSource.cs | 34 ++++++++++++- Source/LibationWinForms/grid/ProductsGrid.cs | 52 +++++--------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Source/LibationWinForms/SyncBindingSource.cs b/Source/LibationWinForms/SyncBindingSource.cs index 49c65eb3..683940bc 100644 --- a/Source/LibationWinForms/SyncBindingSource.cs +++ b/Source/LibationWinForms/SyncBindingSource.cs @@ -1,5 +1,8 @@ -using System; +using ApplicationServices; +using Dinah.Core.DataBinding; +using System; using System.ComponentModel; +using System.Linq; using System.Threading; using System.Windows.Forms; @@ -17,7 +20,34 @@ namespace LibationWinForms public SyncBindingSource(object dataSource, string dataMember) : base(dataSource, dataMember) => syncContext = SynchronizationContext.Current; - protected override void OnListChanged(ListChangedEventArgs e) + public override bool SupportsFiltering => true; + public override string Filter { get => filterString; set => SetFilter(value); } + + private string filterString; + + private void SetFilter(string searchString) + { + if (searchString != filterString) + RemoveFilter(); + + filterString = searchString; + + var searchResults = SearchEngineCommands.Search(searchString); + var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); + + + var allItems = ((SortableBindingList)DataSource).InnerList; + var filterList = productIds.Join(allItems, s => s, ge => ge.AudibleProductId, (pid, ge) => ge).ToList(); + + ((SortableBindingList)DataSource).SetFilteredItems(filterList); + } + + public override void RemoveFilter() + { + ((SortableBindingList)DataSource).RemoveFilter(); + base.RemoveFilter(); + } + protected override void OnListChanged(ListChangedEventArgs e) { if (syncContext is not null) syncContext.Send(_ => base.OnListChanged(e), null); diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index 0270a1e2..9fd4836a 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Drawing; using System.Linq; using System.Threading.Tasks; @@ -55,8 +56,6 @@ namespace LibationWinForms EnableDoubleBuffering(); - // sorting breaks filters. must reapply filters after sorting - _dataGridView.Sorted += reapplyFilter; _dataGridView.CellContentClick += DataGridView_CellContentClick; this.Load += ProductsGrid_Load; @@ -184,14 +183,13 @@ namespace LibationWinForms else bindToGrid(orderedBooks); - // re-apply previous filter - reapplyFilter(); if (!hasBeenDisplayed) { hasBeenDisplayed = true; InitialLoaded?.Invoke(this, new()); } + } private void bindToGrid(List orderedBooks) @@ -230,7 +228,6 @@ namespace LibationWinForms private GridEntry toGridEntry(DataLayer.LibraryBook libraryBook) { var entry = new GridEntry(libraryBook); - entry.Committed += reapplyFilter; // see also notes in Libation/Source/__ARCHITECTURE NOTES.txt :: MVVM entry.LibraryBookUpdated += (sender, _) => _dataGridView.InvalidateRow(_dataGridView.GetRowIdOfBoundItem((GridEntry)sender)); return entry; @@ -240,53 +237,26 @@ namespace LibationWinForms #region Filter - private string _filterSearchString; - private void reapplyFilter(object _ = null, EventArgs __ = null) => Filter(_filterSearchString); public void Filter(string searchString) { - // empty string is valid. null is not - if (searchString is null) - return; - - _filterSearchString = searchString; - if (_dataGridView.Rows.Count == 0) return; - var initVisible = getVisible().Count(); + if (string.IsNullOrEmpty(searchString)) + gridEntryBindingSource.RemoveFilter(); + else + gridEntryBindingSource.Filter = searchString; - var searchResults = SearchEngineCommands.Search(searchString); - var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); - // https://stackoverflow.com/a/18942430 - var bindingContext = BindingContext[_dataGridView.DataSource]; - bindingContext.SuspendBinding(); - { - this.UIThreadSync(() => - { - for (var r = _dataGridView.RowCount - 1; r >= 0; r--) - _dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId); - }); - } - - // Causes repainting of the DataGridView - bindingContext.ResumeBinding(); - - var endVisible = getVisible().Count(); - if (initVisible != endVisible) - VisibleCountChanged?.Invoke(this, endVisible); + VisibleCountChanged?.Invoke(this, bindingList.Count); } #endregion - private IEnumerable getVisible() - => _dataGridView - .AsEnumerable() - .Where(row => row.Visible); - - internal List GetVisible() - => getVisible() - .Select(row => ((GridEntry)row.DataBoundItem).LibraryBook) + internal List GetVisible() + => bindingList + .InnerList + .Select(row => row.LibraryBook) .ToList(); private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem(rowIndex); From 91d6181aec0728ce27a8f91ca12426e0ca0de65c Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 20:15:54 -0600 Subject: [PATCH 02/22] Better naming --- Source/LibationWinForms/SyncBindingSource.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Source/LibationWinForms/SyncBindingSource.cs b/Source/LibationWinForms/SyncBindingSource.cs index 683940bc..c11307ec 100644 --- a/Source/LibationWinForms/SyncBindingSource.cs +++ b/Source/LibationWinForms/SyncBindingSource.cs @@ -21,21 +21,20 @@ namespace LibationWinForms => syncContext = SynchronizationContext.Current; public override bool SupportsFiltering => true; - public override string Filter { get => filterString; set => SetFilter(value); } + public override string Filter { get => FilterString; set => SetFilter(value); } - private string filterString; + private string FilterString; - private void SetFilter(string searchString) + private void SetFilter(string filterString) { - if (searchString != filterString) + if (filterString != FilterString) RemoveFilter(); - filterString = searchString; + FilterString = filterString; - var searchResults = SearchEngineCommands.Search(searchString); + var searchResults = SearchEngineCommands.Search(filterString); var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); - var allItems = ((SortableBindingList)DataSource).InnerList; var filterList = productIds.Join(allItems, s => s, ge => ge.AudibleProductId, (pid, ge) => ge).ToList(); From d1bddeccc83222d1073f70b96ad6afa781d400cd Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 11:16:33 -0600 Subject: [PATCH 03/22] Implement filtering in the sortable binding list. --- Source/LibationWinForms/SyncBindingSource.cs | 24 ---- Source/LibationWinForms/grid/ProductsGrid.cs | 77 +++++------ .../grid/SortableFilterableBindingList.cs | 126 ++++++++++++++++++ 3 files changed, 157 insertions(+), 70 deletions(-) create mode 100644 Source/LibationWinForms/grid/SortableFilterableBindingList.cs diff --git a/Source/LibationWinForms/SyncBindingSource.cs b/Source/LibationWinForms/SyncBindingSource.cs index c11307ec..4aa589ef 100644 --- a/Source/LibationWinForms/SyncBindingSource.cs +++ b/Source/LibationWinForms/SyncBindingSource.cs @@ -21,31 +21,7 @@ namespace LibationWinForms => syncContext = SynchronizationContext.Current; public override bool SupportsFiltering => true; - public override string Filter { get => FilterString; set => SetFilter(value); } - private string FilterString; - - private void SetFilter(string filterString) - { - if (filterString != FilterString) - RemoveFilter(); - - FilterString = filterString; - - var searchResults = SearchEngineCommands.Search(filterString); - var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); - - var allItems = ((SortableBindingList)DataSource).InnerList; - var filterList = productIds.Join(allItems, s => s, ge => ge.AudibleProductId, (pid, ge) => ge).ToList(); - - ((SortableBindingList)DataSource).SetFilteredItems(filterList); - } - - public override void RemoveFilter() - { - ((SortableBindingList)DataSource).RemoveFilter(); - base.RemoveFilter(); - } protected override void OnListChanged(ListChangedEventArgs e) { if (syncContext is not null) diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index 9fd4836a..a654d960 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -159,7 +159,7 @@ namespace LibationWinForms #region UI display functions - private SortableBindingList bindingList; + private SortableFilterableBindingList bindingList; private bool hasBeenDisplayed; public event EventHandler InitialLoaded; @@ -168,69 +168,56 @@ namespace LibationWinForms // don't return early if lib size == 0. this will not update correctly if all books are removed var lib = DbContexts.GetLibrary_Flat_NoTracking(); - var orderedBooks = lib - // default load order - .OrderByDescending(lb => lb.DateAdded) - //// more advanced example: sort by author, then series, then title - //.OrderBy(lb => lb.Book.AuthorNames) - // .ThenBy(lb => lb.Book.SeriesSortable) - // .ThenBy(lb => lb.Book.TitleSortable) - .ToList(); - - // bind - if (bindingList?.Count > 0) - updateGrid(orderedBooks); - else - bindToGrid(orderedBooks); - - if (!hasBeenDisplayed) { + // bind + bindToGrid(lib); hasBeenDisplayed = true; InitialLoaded?.Invoke(this, new()); + VisibleCountChanged?.Invoke(this, bindingList.Count); } + else + updateGrid(lib); } - private void bindToGrid(List orderedBooks) + private void bindToGrid(List orderedBooks) { - bindingList = new SortableBindingList(orderedBooks.Select(lb => toGridEntry(lb))); + bindingList = new SortableFilterableBindingList(orderedBooks.OrderByDescending(lb => lb.DateAdded).Select(lb => new GridEntry(lb))); gridEntryBindingSource.DataSource = bindingList; } - private void updateGrid(List orderedBooks) + private void updateGrid(List dbBooks) { - for (var i = orderedBooks.Count - 1; i >= 0; i--) + int visibleCount = bindingList.Count; + + //Add absent books to grid, or update current books + for (var i = dbBooks.Count - 1; i >= 0; i--) { - var libraryBook = orderedBooks[i]; - var existingItem = bindingList.FirstOrDefault(i => i.AudibleProductId == libraryBook.Book.AudibleProductId); + var libraryBook = dbBooks[i]; + var existingItem = bindingList.AllItems.FirstOrDefault(i => i.AudibleProductId == libraryBook.Book.AudibleProductId); // add new to top if (existingItem is null) - bindingList.Insert(0, toGridEntry(libraryBook)); + bindingList.Insert(0, new GridEntry(libraryBook)); // update existing else existingItem.UpdateLibraryBook(libraryBook); } - // remove deleted from grid. note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this - var oldIds = bindingList.Select(ge => ge.AudibleProductId).ToList(); - var newIds = orderedBooks.Select(lb => lb.Book.AudibleProductId).ToList(); - var remove = oldIds.Except(newIds).ToList(); - foreach (var id in remove) - { - var oldItem = bindingList.FirstOrDefault(ge => ge.AudibleProductId == id); - if (oldItem is not null) - bindingList.Remove(oldItem); - } - } + // remove deleted from grid. + // note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this + var removedBooks = + bindingList + .AllItems + .ExceptBy(dbBooks.Select(lb => lb.Book.AudibleProductId), ge => ge.AudibleProductId) + .ToList(); - private GridEntry toGridEntry(DataLayer.LibraryBook libraryBook) - { - var entry = new GridEntry(libraryBook); - // see also notes in Libation/Source/__ARCHITECTURE NOTES.txt :: MVVM - entry.LibraryBookUpdated += (sender, _) => _dataGridView.InvalidateRow(_dataGridView.GetRowIdOfBoundItem((GridEntry)sender)); - return entry; + foreach (var removed in removedBooks) + bindingList.Remove(removed); + + if (bindingList.Count != visibleCount) + VisibleCountChanged?.Invoke(this, bindingList.Count); } #endregion @@ -239,23 +226,21 @@ namespace LibationWinForms public void Filter(string searchString) { - if (_dataGridView.Rows.Count == 0) - return; + int visibleCount = bindingList.Count; if (string.IsNullOrEmpty(searchString)) gridEntryBindingSource.RemoveFilter(); else gridEntryBindingSource.Filter = searchString; - - VisibleCountChanged?.Invoke(this, bindingList.Count); + if (visibleCount != bindingList.Count) + VisibleCountChanged?.Invoke(this, bindingList.Count); } #endregion internal List GetVisible() => bindingList - .InnerList .Select(row => row.LibraryBook) .ToList(); diff --git a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs new file mode 100644 index 00000000..dd69d1b9 --- /dev/null +++ b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs @@ -0,0 +1,126 @@ +using ApplicationServices; +using Dinah.Core.DataBinding; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace LibationWinForms +{ + /* + * Allows filtering of the underlying SortableBindingList + * by implementing IBindingListView aud using SearchEngineCommands + * + * When filtering is applied, the filtered-out items are removed + * from the base list and added to the private FilterRemoved list. + * All items, filtered or not, are stored in the private AllItems + * list. When filtering is removed, items in the FilterRemoved list + * are added back to the base list. + * + * Remove and InsertItem are overridden to ensure that the current + * filter remains applied when items are removed/added to the list. + */ + internal class SortableFilterableBindingList : SortableBindingList, IBindingListView + { + /// + /// Items that were removed from the list due to filtering + /// + private readonly List FilterRemoved = new(); + /// + /// Tracks all items in the list, both filtered and not. + /// + public readonly List AllItems; + private string FilterString; + private Action Sort; + public SortableFilterableBindingList(IEnumerable enumeration) : base(enumeration) + { + AllItems = new List(Items); + + //This is only necessary because SortableBindingList doesn't expose Sort() + //You should make SortableBindingList.Sort protected and remove reflection + var method = typeof(SortableBindingList).GetMethod("Sort", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + Sort = method.CreateDelegate(this); + } + + public bool SupportsFiltering => true; + public string Filter { get => FilterString; set => ApplyFilter(value); } + + #region Unused - Advanced Filtering + public bool SupportsAdvancedSorting => false; + + + //This ApplySort overload is only called is SupportsAdvancedSorting is true. + //Otherwise BindingList.ApplySort() is used + public void ApplySort(ListSortDescriptionCollection sorts) => throw new NotImplementedException(); + + public ListSortDescriptionCollection SortDescriptions => throw new NotImplementedException(); + #endregion + + public new void Remove(GridEntry entry) + { + AllItems.Remove(entry); + FilterRemoved.Remove(entry); + base.Remove(entry); + } + + protected override void InsertItem(int index, GridEntry item) + { + AllItems.Insert(index, item); + + if (FilterString is not null) + { + var searchResults = SearchEngineCommands.Search(FilterString); + //Decide if the new item matches the filter, and either insert it in the + //displayed items or the filtered out items list + if (searchResults.Docs.Any(d => d.ProductId == item.AudibleProductId)) + base.InsertItem(index, item); + else + FilterRemoved.Add(item); + } + else + base.InsertItem(index, item); + } + + private void ApplyFilter(string filterString) + { + if (filterString != FilterString) + RemoveFilter(); + + FilterString = filterString; + + var searchResults = SearchEngineCommands.Search(filterString); + var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); + + for (int i = Items.Count - 1; i >= 0; i--) + { + if (!productIds.Contains(Items[i].AudibleProductId)) + { + FilterRemoved.Add(Items[i]); + Items.RemoveAt(i); + base.OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, i)); + } + } + } + + public void RemoveFilter() + { + if (FilterString is null) return; + + for (int i = 0; i < FilterRemoved.Count; i++) + { + Items.Insert(i, FilterRemoved[i]); + base.OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, i)); + } + + FilterRemoved.Clear(); + + if (IsSortedCore) + Sort(); + else + //No user-defined sort is applied, so do default sorting by date added, descending + ((List)Items).Sort((i1,i2) =>i2.LibraryBook.DateAdded.CompareTo(i1.LibraryBook.DateAdded)); + + FilterString = null; + } + } +} From e1e265a101856aefddca3525e72550d4930d15fe Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 11:38:24 -0600 Subject: [PATCH 04/22] Don't filter after every insert --- Source/LibationWinForms/grid/ProductsGrid.cs | 5 +++++ .../grid/SortableFilterableBindingList.cs | 18 +++--------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index a654d960..fea731d6 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -204,6 +204,11 @@ namespace LibationWinForms else existingItem.UpdateLibraryBook(libraryBook); } + + //refilter for newly added items + string existingFilter = gridEntryBindingSource.Filter; + Filter(null); + Filter(existingFilter); // remove deleted from grid. // note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this diff --git a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs index dd69d1b9..8dff9d35 100644 --- a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs +++ b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs @@ -66,19 +66,7 @@ namespace LibationWinForms protected override void InsertItem(int index, GridEntry item) { AllItems.Insert(index, item); - - if (FilterString is not null) - { - var searchResults = SearchEngineCommands.Search(FilterString); - //Decide if the new item matches the filter, and either insert it in the - //displayed items or the filtered out items list - if (searchResults.Docs.Any(d => d.ProductId == item.AudibleProductId)) - base.InsertItem(index, item); - else - FilterRemoved.Add(item); - } - else - base.InsertItem(index, item); + base.InsertItem(index, item); } private void ApplyFilter(string filterString) @@ -89,11 +77,11 @@ namespace LibationWinForms FilterString = filterString; var searchResults = SearchEngineCommands.Search(filterString); - var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); + var filteredOut = Items.ExceptBy(searchResults.Docs.Select(d=>d.ProductId), ge=>ge.AudibleProductId); for (int i = Items.Count - 1; i >= 0; i--) { - if (!productIds.Contains(Items[i].AudibleProductId)) + if (filteredOut.Contains(Items[i])) { FilterRemoved.Add(Items[i]); Items.RemoveAt(i); From 98a552e9af3add4c5f0a3526796dd5903e226079 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 11:46:42 -0600 Subject: [PATCH 05/22] Optimization --- Source/LibationWinForms/grid/ProductsGrid.cs | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index fea731d6..dec86842 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Drawing; using System.Linq; using System.Threading.Tasks; @@ -8,8 +7,6 @@ using System.Windows.Forms; using ApplicationServices; using DataLayer; using Dinah.Core; -using Dinah.Core.DataBinding; -using Dinah.Core.Threading; using Dinah.Core.Windows.Forms; using FileLiberator; using LibationFileManager; @@ -181,15 +178,16 @@ namespace LibationWinForms } - private void bindToGrid(List orderedBooks) + private void bindToGrid(List dbBooks) { - bindingList = new SortableFilterableBindingList(orderedBooks.OrderByDescending(lb => lb.DateAdded).Select(lb => new GridEntry(lb))); + bindingList = new SortableFilterableBindingList(dbBooks.OrderByDescending(lb => lb.DateAdded).Select(lb => new GridEntry(lb))); gridEntryBindingSource.DataSource = bindingList; } private void updateGrid(List dbBooks) { int visibleCount = bindingList.Count; + string existingFilter = gridEntryBindingSource.Filter; //Add absent books to grid, or update current books for (var i = dbBooks.Count - 1; i >= 0; i--) @@ -204,11 +202,13 @@ namespace LibationWinForms else existingItem.UpdateLibraryBook(libraryBook); } - - //refilter for newly added items - string existingFilter = gridEntryBindingSource.Filter; - Filter(null); - Filter(existingFilter); + + if (bindingList.Count != visibleCount) + { + //refilter for newly added items + Filter(null); + Filter(existingFilter); + } // remove deleted from grid. // note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this @@ -219,6 +219,7 @@ namespace LibationWinForms .ToList(); foreach (var removed in removedBooks) + //no need to re-filter for removed books bindingList.Remove(removed); if (bindingList.Count != visibleCount) From 1b9c4cfc23bad0c935be780a77a8e515e88c4972 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 11:47:34 -0600 Subject: [PATCH 06/22] Remove unused usings --- Source/LibationWinForms/SyncBindingSource.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/LibationWinForms/SyncBindingSource.cs b/Source/LibationWinForms/SyncBindingSource.cs index 4aa589ef..42d38ad9 100644 --- a/Source/LibationWinForms/SyncBindingSource.cs +++ b/Source/LibationWinForms/SyncBindingSource.cs @@ -1,8 +1,5 @@ -using ApplicationServices; -using Dinah.Core.DataBinding; -using System; +using System; using System.ComponentModel; -using System.Linq; using System.Threading; using System.Windows.Forms; From 9721890a3c85aeea95fe9aea48b6d6d3df232453 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 11:50:11 -0600 Subject: [PATCH 07/22] Update documentation --- Source/LibationWinForms/grid/SortableFilterableBindingList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs index 8dff9d35..a5a5debb 100644 --- a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs +++ b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs @@ -17,8 +17,8 @@ namespace LibationWinForms * list. When filtering is removed, items in the FilterRemoved list * are added back to the base list. * - * Remove and InsertItem are overridden to ensure that the current - * filter remains applied when items are removed/added to the list. + * Remove and InsertItem are overridden to ensure that the base + * list remains synchronized with the AllItems list. */ internal class SortableFilterableBindingList : SortableBindingList, IBindingListView { From 5dcdf670beac37a3a65729f7710bdb418a6f1d99 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 11:58:36 -0600 Subject: [PATCH 08/22] Simplify RemoveFilter --- .../grid/SortableFilterableBindingList.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs index a5a5debb..a5aab5ca 100644 --- a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs +++ b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs @@ -9,7 +9,7 @@ namespace LibationWinForms { /* * Allows filtering of the underlying SortableBindingList - * by implementing IBindingListView aud using SearchEngineCommands + * by implementing IBindingListView and using SearchEngineCommands * * When filtering is applied, the filtered-out items are removed * from the base list and added to the private FilterRemoved list. @@ -93,12 +93,9 @@ namespace LibationWinForms public void RemoveFilter() { if (FilterString is null) return; - + for (int i = 0; i < FilterRemoved.Count; i++) - { - Items.Insert(i, FilterRemoved[i]); - base.OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, i)); - } + Insert(i, FilterRemoved[i]); FilterRemoved.Clear(); From 7b8a4e4d72255ccd31394a3d295eaa16e6004cfb Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 12:06:41 -0600 Subject: [PATCH 09/22] Simplify filtering --- .../grid/SortableFilterableBindingList.cs | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs index a5aab5ca..eaa244a3 100644 --- a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs +++ b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs @@ -13,12 +13,11 @@ namespace LibationWinForms * * When filtering is applied, the filtered-out items are removed * from the base list and added to the private FilterRemoved list. - * All items, filtered or not, are stored in the private AllItems - * list. When filtering is removed, items in the FilterRemoved list - * are added back to the base list. + * When filtering is removed, items in the FilterRemoved list are + * added back to the base list. * - * Remove and InsertItem are overridden to ensure that the base - * list remains synchronized with the AllItems list. + * Remove is overridden to ensure that removed items are removed from + * the base list (visible items) as well as the FilterRemoved list. */ internal class SortableFilterableBindingList : SortableBindingList, IBindingListView { @@ -29,13 +28,10 @@ namespace LibationWinForms /// /// Tracks all items in the list, both filtered and not. /// - public readonly List AllItems; private string FilterString; private Action Sort; public SortableFilterableBindingList(IEnumerable enumeration) : base(enumeration) { - AllItems = new List(Items); - //This is only necessary because SortableBindingList doesn't expose Sort() //You should make SortableBindingList.Sort protected and remove reflection var method = typeof(SortableBindingList).GetMethod("Sort", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); @@ -58,16 +54,11 @@ namespace LibationWinForms public new void Remove(GridEntry entry) { - AllItems.Remove(entry); FilterRemoved.Remove(entry); base.Remove(entry); } - protected override void InsertItem(int index, GridEntry item) - { - AllItems.Insert(index, item); - base.InsertItem(index, item); - } + public List AllItems => Items.Concat(FilterRemoved).ToList(); private void ApplyFilter(string filterString) { @@ -95,7 +86,7 @@ namespace LibationWinForms if (FilterString is null) return; for (int i = 0; i < FilterRemoved.Count; i++) - Insert(i, FilterRemoved[i]); + base.InsertItem(i, FilterRemoved[i]); FilterRemoved.Clear(); From c28872544c78220a4142e7b43879465365c069ee Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 12:10:54 -0600 Subject: [PATCH 10/22] Don't call concat for every book. --- Source/LibationWinForms/grid/ProductsGrid.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index dec86842..6ae814f3 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -190,10 +190,12 @@ namespace LibationWinForms string existingFilter = gridEntryBindingSource.Filter; //Add absent books to grid, or update current books + + var allItmes = bindingList.AllItems; for (var i = dbBooks.Count - 1; i >= 0; i--) { var libraryBook = dbBooks[i]; - var existingItem = bindingList.AllItems.FirstOrDefault(i => i.AudibleProductId == libraryBook.Book.AudibleProductId); + var existingItem = allItmes.FirstOrDefault(i => i.AudibleProductId == libraryBook.Book.AudibleProductId); // add new to top if (existingItem is null) From aabc14c639cb32490f6cd9fb1b37123262cf6f6f Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 12:12:34 -0600 Subject: [PATCH 11/22] Make AllItems a method --- Source/LibationWinForms/grid/ProductsGrid.cs | 4 ++-- Source/LibationWinForms/grid/SortableFilterableBindingList.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index 6ae814f3..42632f69 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -191,7 +191,7 @@ namespace LibationWinForms //Add absent books to grid, or update current books - var allItmes = bindingList.AllItems; + var allItmes = bindingList.AllItems(); for (var i = dbBooks.Count - 1; i >= 0; i--) { var libraryBook = dbBooks[i]; @@ -216,7 +216,7 @@ namespace LibationWinForms // note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this var removedBooks = bindingList - .AllItems + .AllItems() .ExceptBy(dbBooks.Select(lb => lb.Book.AudibleProductId), ge => ge.AudibleProductId) .ToList(); diff --git a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs index eaa244a3..54f98b03 100644 --- a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs +++ b/Source/LibationWinForms/grid/SortableFilterableBindingList.cs @@ -58,7 +58,7 @@ namespace LibationWinForms base.Remove(entry); } - public List AllItems => Items.Concat(FilterRemoved).ToList(); + public List AllItems() => Items.Concat(FilterRemoved).ToList(); private void ApplyFilter(string filterString) { From 89059510fd4d1286ce4e3ab89eb2b3b91f499f83 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 12:37:56 -0600 Subject: [PATCH 12/22] More logical naming --- ...terableBindingList.cs => FilterableSortableBindingList.cs} | 4 ++-- Source/LibationWinForms/grid/ProductsGrid.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename Source/LibationWinForms/grid/{SortableFilterableBindingList.cs => FilterableSortableBindingList.cs} (96%) diff --git a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs b/Source/LibationWinForms/grid/FilterableSortableBindingList.cs similarity index 96% rename from Source/LibationWinForms/grid/SortableFilterableBindingList.cs rename to Source/LibationWinForms/grid/FilterableSortableBindingList.cs index 54f98b03..e0f83776 100644 --- a/Source/LibationWinForms/grid/SortableFilterableBindingList.cs +++ b/Source/LibationWinForms/grid/FilterableSortableBindingList.cs @@ -19,7 +19,7 @@ namespace LibationWinForms * Remove is overridden to ensure that removed items are removed from * the base list (visible items) as well as the FilterRemoved list. */ - internal class SortableFilterableBindingList : SortableBindingList, IBindingListView + internal class FilterableSortableBindingList : SortableBindingList, IBindingListView { /// /// Items that were removed from the list due to filtering @@ -30,7 +30,7 @@ namespace LibationWinForms /// private string FilterString; private Action Sort; - public SortableFilterableBindingList(IEnumerable enumeration) : base(enumeration) + public FilterableSortableBindingList(IEnumerable enumeration) : base(enumeration) { //This is only necessary because SortableBindingList doesn't expose Sort() //You should make SortableBindingList.Sort protected and remove reflection diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index 42632f69..50c4ebdd 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -156,7 +156,7 @@ namespace LibationWinForms #region UI display functions - private SortableFilterableBindingList bindingList; + private FilterableSortableBindingList bindingList; private bool hasBeenDisplayed; public event EventHandler InitialLoaded; @@ -180,7 +180,7 @@ namespace LibationWinForms private void bindToGrid(List dbBooks) { - bindingList = new SortableFilterableBindingList(dbBooks.OrderByDescending(lb => lb.DateAdded).Select(lb => new GridEntry(lb))); + bindingList = new FilterableSortableBindingList(dbBooks.OrderByDescending(lb => lb.DateAdded).Select(lb => new GridEntry(lb))); gridEntryBindingSource.DataSource = bindingList; } From 3535156ea5e5eeb1bc47cb2c491cbfe379d620bf Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 12:47:50 -0600 Subject: [PATCH 13/22] Edit --- .../grid/FilterableSortableBindingList.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Source/LibationWinForms/grid/FilterableSortableBindingList.cs b/Source/LibationWinForms/grid/FilterableSortableBindingList.cs index e0f83776..4552d5bb 100644 --- a/Source/LibationWinForms/grid/FilterableSortableBindingList.cs +++ b/Source/LibationWinForms/grid/FilterableSortableBindingList.cs @@ -22,12 +22,9 @@ namespace LibationWinForms internal class FilterableSortableBindingList : SortableBindingList, IBindingListView { /// - /// Items that were removed from the list due to filtering + /// Items that were removed from the base list due to filtering /// private readonly List FilterRemoved = new(); - /// - /// Tracks all items in the list, both filtered and not. - /// private string FilterString; private Action Sort; public FilterableSortableBindingList(IEnumerable enumeration) : base(enumeration) @@ -44,8 +41,7 @@ namespace LibationWinForms #region Unused - Advanced Filtering public bool SupportsAdvancedSorting => false; - - //This ApplySort overload is only called is SupportsAdvancedSorting is true. + //This ApplySort overload if only called is SupportsAdvancedSorting is true. //Otherwise BindingList.ApplySort() is used public void ApplySort(ListSortDescriptionCollection sorts) => throw new NotImplementedException(); @@ -58,6 +54,7 @@ namespace LibationWinForms base.Remove(entry); } + /// All items in the list, including those filtered out. public List AllItems() => Items.Concat(FilterRemoved).ToList(); private void ApplyFilter(string filterString) @@ -68,7 +65,7 @@ namespace LibationWinForms FilterString = filterString; var searchResults = SearchEngineCommands.Search(filterString); - var filteredOut = Items.ExceptBy(searchResults.Docs.Select(d=>d.ProductId), ge=>ge.AudibleProductId); + var filteredOut = Items.ExceptBy(searchResults.Docs.Select(d => d.ProductId), ge => ge.AudibleProductId); for (int i = Items.Count - 1; i >= 0; i--) { @@ -84,7 +81,7 @@ namespace LibationWinForms public void RemoveFilter() { if (FilterString is null) return; - + for (int i = 0; i < FilterRemoved.Count; i++) base.InsertItem(i, FilterRemoved[i]); @@ -94,7 +91,7 @@ namespace LibationWinForms Sort(); else //No user-defined sort is applied, so do default sorting by date added, descending - ((List)Items).Sort((i1,i2) =>i2.LibraryBook.DateAdded.CompareTo(i1.LibraryBook.DateAdded)); + ((List)Items).Sort((i1, i2) => i2.LibraryBook.DateAdded.CompareTo(i1.LibraryBook.DateAdded)); FilterString = null; } From 9b5df99a618b512517ea2ac90f3f0a033318ec3f Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 12:56:15 -0600 Subject: [PATCH 14/22] Use new ProcessQueue --- Source/LibationWinForms/Form1.VisibleBooks.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/LibationWinForms/Form1.VisibleBooks.cs b/Source/LibationWinForms/Form1.VisibleBooks.cs index 848f6c09..c0d763c1 100644 --- a/Source/LibationWinForms/Form1.VisibleBooks.cs +++ b/Source/LibationWinForms/Form1.VisibleBooks.cs @@ -63,8 +63,7 @@ namespace LibationWinForms } private async void liberateVisible(object sender, EventArgs e) - => await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(productsGrid.GetVisible()); - + => await Task.Run(() => processBookQueue1.AddDownloadDecrypt(productsGrid.GetVisible())); private void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e) { var dialog = new TagsBatchDialog(); From e51c30462f481beef8cd2bff52580fd82f10f499 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 13:16:11 -0600 Subject: [PATCH 15/22] Revert "Use new ProcessQueue" This reverts commit 9b5df99a618b512517ea2ac90f3f0a033318ec3f. --- Source/LibationWinForms/Form1.VisibleBooks.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/LibationWinForms/Form1.VisibleBooks.cs b/Source/LibationWinForms/Form1.VisibleBooks.cs index c0d763c1..848f6c09 100644 --- a/Source/LibationWinForms/Form1.VisibleBooks.cs +++ b/Source/LibationWinForms/Form1.VisibleBooks.cs @@ -63,7 +63,8 @@ namespace LibationWinForms } private async void liberateVisible(object sender, EventArgs e) - => await Task.Run(() => processBookQueue1.AddDownloadDecrypt(productsGrid.GetVisible())); + => await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(productsGrid.GetVisible()); + private void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e) { var dialog = new TagsBatchDialog(); From 15396c611a9afe462ebba347a3386676adce0477 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 13:16:50 -0600 Subject: [PATCH 16/22] Add documentation --- Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index b0fa7aa3..e05ca354 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -255,6 +255,7 @@ namespace LibationWinForms.ProcessQueue /// Updates the display of a single at within /// /// index of the within the + /// The nme of the property that needs updating. If null, all properties are updated. private void UpdateControl(int queueIndex, string propertyName = null) { int i = queueIndex - FirstVisible; From 109ce0dd1f82848cb1dcca99ea92e150526d5a93 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 14:26:43 -0600 Subject: [PATCH 17/22] overwrite cached state --- Source/LibationWinForms/grid/GridEntry.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/LibationWinForms/grid/GridEntry.cs b/Source/LibationWinForms/grid/GridEntry.cs index 7040316c..07ecb838 100644 --- a/Source/LibationWinForms/grid/GridEntry.cs +++ b/Source/LibationWinForms/grid/GridEntry.cs @@ -160,10 +160,12 @@ namespace LibationWinForms break; case nameof(udi.BookStatus): Book.UserDefinedItem.BookStatus = udi.BookStatus; + _bookStatus = udi.BookStatus; NotifyPropertyChanged(nameof(Liberate)); break; case nameof(udi.PdfStatus): Book.UserDefinedItem.PdfStatus = udi.PdfStatus; + _pdfStatus = udi.PdfStatus; NotifyPropertyChanged(nameof(Liberate)); break; } From 843fddabde1c2f327ff8bce1efcac415445bc0b4 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 14:27:34 -0600 Subject: [PATCH 18/22] Changes discussed in email --- Source/LibationWinForms/Form1.Designer.cs | 24 +++--- Source/LibationWinForms/Form1.Liberate.cs | 17 ++++- Source/LibationWinForms/Form1.ProcessQueue.cs | 74 +++++++++++++------ Source/LibationWinForms/Form1.VisibleBooks.cs | 5 +- .../ProcessQueue/ProcessQueueControl.cs | 11 ++- Source/LibationWinForms/grid/ProductsGrid.cs | 17 +---- 6 files changed, 90 insertions(+), 58 deletions(-) diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs index b6be6147..b4cf9508 100644 --- a/Source/LibationWinForms/Form1.Designer.cs +++ b/Source/LibationWinForms/Form1.Designer.cs @@ -73,7 +73,7 @@ this.addQuickFilterBtn = new System.Windows.Forms.Button(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.panel1 = new System.Windows.Forms.Panel(); - this.hideQueueBtn = new System.Windows.Forms.Button(); + this.toggleQueueHideBtn = new System.Windows.Forms.Button(); this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessQueueControl(); this.menuStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout(); @@ -462,7 +462,7 @@ // panel1 // this.panel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.panel1.Controls.Add(this.hideQueueBtn); + this.panel1.Controls.Add(this.toggleQueueHideBtn); this.panel1.Controls.Add(this.gridPanel); this.panel1.Controls.Add(this.addQuickFilterBtn); this.panel1.Controls.Add(this.filterHelpBtn); @@ -477,15 +477,15 @@ // // hideQueueBtn // - this.hideQueueBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.hideQueueBtn.Location = new System.Drawing.Point(966, 4); - this.hideQueueBtn.Margin = new System.Windows.Forms.Padding(5, 4, 17, 4); - this.hideQueueBtn.Name = "hideQueueBtn"; - this.hideQueueBtn.Size = new System.Drawing.Size(38, 36); - this.hideQueueBtn.TabIndex = 8; - this.hideQueueBtn.Text = "❰❰❰"; - this.hideQueueBtn.UseVisualStyleBackColor = true; - this.hideQueueBtn.Click += new System.EventHandler(this.HideQueueBtn_Click); + this.toggleQueueHideBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.toggleQueueHideBtn.Location = new System.Drawing.Point(966, 4); + this.toggleQueueHideBtn.Margin = new System.Windows.Forms.Padding(5, 4, 17, 4); + this.toggleQueueHideBtn.Name = "hideQueueBtn"; + this.toggleQueueHideBtn.Size = new System.Drawing.Size(38, 36); + this.toggleQueueHideBtn.TabIndex = 8; + this.toggleQueueHideBtn.Text = "❱❱❱"; + this.toggleQueueHideBtn.UseVisualStyleBackColor = true; + this.toggleQueueHideBtn.Click += new System.EventHandler(this.ToggleQueueHideBtn_Click); // // processBookQueue1 // @@ -571,6 +571,6 @@ private System.Windows.Forms.SplitContainer splitContainer1; private LibationWinForms.ProcessQueue.ProcessQueueControl processBookQueue1; private System.Windows.Forms.Panel panel1; - private System.Windows.Forms.Button hideQueueBtn; + private System.Windows.Forms.Button toggleQueueHideBtn; } } diff --git a/Source/LibationWinForms/Form1.Liberate.cs b/Source/LibationWinForms/Form1.Liberate.cs index d1369d7a..66a24ebf 100644 --- a/Source/LibationWinForms/Form1.Liberate.cs +++ b/Source/LibationWinForms/Form1.Liberate.cs @@ -11,12 +11,18 @@ namespace LibationWinForms //GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => await Task.Run(() => processBookQueue1.AddDownloadDecrypt(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() + { + SetQueueCollapseState(false); + await Task.Run(() => processBookQueue1.AddDownloadDecrypt(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() .Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated || lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.NotLiberated))); + } private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => await Task.Run(() => processBookQueue1.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() - .Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated))); + { + SetQueueCollapseState(false); + await Task.Run(() => processBookQueue1.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() + .Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated))); + } private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e) { @@ -29,8 +35,11 @@ namespace LibationWinForms MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (result == DialogResult.Yes) + { + SetQueueCollapseState(false); await Task.Run(() => processBookQueue1.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() - .Where(lb=>lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.Liberated))); + .Where(lb => lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.Liberated))); + } //Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing. } } diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index acf4ac66..74d30406 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -1,41 +1,73 @@ -using ApplicationServices; +using DataLayer; +using Dinah.Core; using LibationFileManager; using LibationWinForms.ProcessQueue; using System; using System.Linq; -using System.Threading.Tasks; using System.Windows.Forms; namespace LibationWinForms { public partial class Form1 { - private void Configure_ProcessQueue() - { - productsGrid.LiberateClicked += (_, lb) => processBookQueue1.AddDownloadDecrypt(lb); - processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; - } int WidthChange = 0; - private void HideQueueBtn_Click(object sender, EventArgs e) + private void Configure_ProcessQueue() { - if (splitContainer1.Panel2Collapsed) + productsGrid.LiberateClicked += ProductsGrid_LiberateClicked; + processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; + var coppalseState = Configuration.Instance.GetNonString(nameof(splitContainer1.Panel2Collapsed)); + WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth; + SetQueueCollapseState(coppalseState); + } + + private void ProductsGrid_LiberateClicked(object sender, LibraryBook e) + { + if (e.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated) { - WidthChange = WidthChange == 0 ? splitContainer1.Panel2.Width + splitContainer1.SplitterWidth : WidthChange; - Width += WidthChange; - splitContainer1.Panel2.Controls.Add(processBookQueue1); - splitContainer1.Panel2Collapsed = false; - processBookQueue1.popoutBtn.Visible = true; - hideQueueBtn.Text = "❰❰❰"; + SetQueueCollapseState(false); + processBookQueue1.AddDownloadDecrypt(e); } - else + else if (e.Book.UserDefinedItem.PdfStatus is not null and LiberatedStatus.NotLiberated) + { + SetQueueCollapseState(false); + processBookQueue1.AddDownloadPdf(e); + } + else if (e.Book.Audio_Exists()) + { + // liberated: open explorer to file + var filePath = AudibleFileStorage.Audio.GetPath(e.Book.AudibleProductId); + if (!Go.To.File(filePath)) + { + var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}"; + MessageBox.Show($"File not found" + suffix); + } + } + } + + private void SetQueueCollapseState(bool collapsed) + { + if (collapsed && !splitContainer1.Panel2Collapsed) { WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth; splitContainer1.Panel2.Controls.Remove(processBookQueue1); splitContainer1.Panel2Collapsed = true; Width -= WidthChange; - hideQueueBtn.Text = "❱❱❱"; } + else if (!collapsed && splitContainer1.Panel2Collapsed) + { + Width += WidthChange; + splitContainer1.Panel2.Controls.Add(processBookQueue1); + splitContainer1.Panel2Collapsed = false; + processBookQueue1.popoutBtn.Visible = true; + } + toggleQueueHideBtn.Text = splitContainer1.Panel2Collapsed ? "❰❰❰" : "❱❱❱"; + } + + private void ToggleQueueHideBtn_Click(object sender, EventArgs e) + { + SetQueueCollapseState(!splitContainer1.Panel2Collapsed); + Configuration.Instance.SetObject(nameof(splitContainer1.Panel2Collapsed), splitContainer1.Panel2Collapsed); } private void ProcessBookQueue1_PopOut(object sender, EventArgs e) @@ -50,8 +82,8 @@ namespace LibationWinForms dockForm.PassControl(processBookQueue1); dockForm.Show(); this.Width -= dockForm.WidthChange; - hideQueueBtn.Visible = false; - int deltax = filterBtn.Margin.Right + hideQueueBtn.Width + hideQueueBtn.Margin.Left; + toggleQueueHideBtn.Visible = false; + int deltax = filterBtn.Margin.Right + toggleQueueHideBtn.Width + toggleQueueHideBtn.Margin.Left; filterBtn.Location= new System.Drawing.Point(filterBtn.Location.X + deltax, filterBtn.Location.Y); filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X + deltax, filterSearchTb.Location.Y); } @@ -66,8 +98,8 @@ namespace LibationWinForms processBookQueue1.popoutBtn.Visible = true; dockForm.SaveSizeAndLocation(Configuration.Instance); this.Focus(); - hideQueueBtn.Visible = true; - int deltax = filterBtn.Margin.Right + hideQueueBtn.Width + hideQueueBtn.Margin.Left; + toggleQueueHideBtn.Visible = true; + int deltax = filterBtn.Margin.Right + toggleQueueHideBtn.Width + toggleQueueHideBtn.Margin.Left; filterBtn.Location = new System.Drawing.Point(filterBtn.Location.X - deltax, filterBtn.Location.Y); filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X - deltax, filterSearchTb.Location.Y); } diff --git a/Source/LibationWinForms/Form1.VisibleBooks.cs b/Source/LibationWinForms/Form1.VisibleBooks.cs index 0890ddb3..65dfecca 100644 --- a/Source/LibationWinForms/Form1.VisibleBooks.cs +++ b/Source/LibationWinForms/Form1.VisibleBooks.cs @@ -61,7 +61,10 @@ namespace LibationWinForms } private async void liberateVisible(object sender, EventArgs e) - => await Task.Run(() => processBookQueue1.AddDownloadDecrypt(productsGrid.GetVisible())); + { + SetQueueCollapseState(false); + await Task.Run(() => processBookQueue1.AddDownloadDecrypt(productsGrid.GetVisible())); + } private void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e) { diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index e05ca354..59442a1c 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -42,6 +42,8 @@ namespace LibationWinForms.ProcessQueue public bool Running => !QueueRunner?.IsCompleted ?? false; public ToolStripButton popoutBtn = new(); + private System.Threading.SynchronizationContext syncContext { get; } = System.Threading.SynchronizationContext.Current; + public ProcessQueueControl() { InitializeComponent(); @@ -122,12 +124,13 @@ namespace LibationWinForms.ProcessQueue private void AddToQueue(ProcessBook pbook) { - BeginInvoke(() => + syncContext.Post(_ => { Queue.Enqueue(pbook); if (!Running) QueueRunner = QueueLoop(); - }); + }, + null); } DateTime StartintTime; @@ -264,7 +267,7 @@ namespace LibationWinForms.ProcessQueue var proc = Queue[queueIndex]; - Panels[i].Invoke(() => + syncContext.Send(_ => { Panels[i].SuspendLayout(); if (propertyName is null || propertyName == nameof(proc.Cover)) @@ -285,7 +288,7 @@ namespace LibationWinForms.ProcessQueue if (propertyName is null || propertyName == nameof(proc.TimeRemaining)) Panels[i].SetRemainingTime(proc.TimeRemaining); Panels[i].ResumeLayout(); - }); + }, null); } private void UpdateAllControls() diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index 50c4ebdd..690cb00d 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using System.Windows.Forms; using ApplicationServices; using DataLayer; -using Dinah.Core; using Dinah.Core.Windows.Forms; using FileLiberator; using LibationFileManager; @@ -128,20 +127,6 @@ namespace LibationWinForms private void Liberate_Click(GridEntry liveGridEntry) { - var libraryBook = liveGridEntry.LibraryBook; - - // liberated: open explorer to file - if (libraryBook.Book.Audio_Exists()) - { - var filePath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); - if (!Go.To.File(filePath)) - { - var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}"; - MessageBox.Show($"File not found" + suffix); - } - return; - } - LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook); } @@ -207,7 +192,7 @@ namespace LibationWinForms if (bindingList.Count != visibleCount) { - //refilter for newly added items + //re-filter for newly added items Filter(null); Filter(existingFilter); } From 29a50bb6401ff4ccaaeda8b3dc5e1d00fa24aa83 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 14:31:03 -0600 Subject: [PATCH 19/22] typo --- Source/LibationWinForms/grid/FilterableSortableBindingList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/LibationWinForms/grid/FilterableSortableBindingList.cs b/Source/LibationWinForms/grid/FilterableSortableBindingList.cs index 4552d5bb..3adfc663 100644 --- a/Source/LibationWinForms/grid/FilterableSortableBindingList.cs +++ b/Source/LibationWinForms/grid/FilterableSortableBindingList.cs @@ -41,7 +41,7 @@ namespace LibationWinForms #region Unused - Advanced Filtering public bool SupportsAdvancedSorting => false; - //This ApplySort overload if only called is SupportsAdvancedSorting is true. + //This ApplySort overload is only called if SupportsAdvancedSorting is true. //Otherwise BindingList.ApplySort() is used public void ApplySort(ListSortDescriptionCollection sorts) => throw new NotImplementedException(); From 6d7f2344979b7ed872c420fbe802f613b439e99d Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 14:32:59 -0600 Subject: [PATCH 20/22] Remove unnecessary base form --- .../BaseForms/LiberationBaseForm.cs | 164 ------------------ .../BookLiberation/DownloadForm.cs | 30 +++- 2 files changed, 24 insertions(+), 170 deletions(-) delete mode 100644 Source/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs diff --git a/Source/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs b/Source/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs deleted file mode 100644 index 03f5ba05..00000000 --- a/Source/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Windows.Forms; -using DataLayer; -using Dinah.Core.Net.Http; -using Dinah.Core.Threading; -using FileLiberator; - -namespace LibationWinForms.BookLiberation.BaseForms -{ - public class LiberationBaseForm : Form - { - protected Streamable Streamable { get; private set; } - protected LogMe LogMe { get; private set; } - private SynchronizeInvoker Invoker { get; init; } - - public LiberationBaseForm() - { - //SynchronizationContext.Current will be null until the process contains a Form. - //If this is the first form created, it will not exist until after execution - //reaches inside the constructor (after base class has been initialized). - Invoker = new SynchronizeInvoker(); - this.SetLibationIcon(); - } - - public void RegisterFileLiberator(Streamable streamable, LogMe logMe = null) - { - if (streamable is null) return; - - Streamable = streamable; - LogMe = logMe; - - Subscribe(streamable); - - if (Streamable is Processable processable) - Subscribe(processable); - if (Streamable is AudioDecodable audioDecodable) - Subscribe(audioDecodable); - } - - #region Event Subscribers and Unsubscribers - private void Subscribe(Streamable streamable) - { - UnsubscribeStreamable(this, EventArgs.Empty); - - streamable.StreamingBegin += OnStreamingBeginShow; - streamable.StreamingBegin += Streamable_StreamingBegin; - streamable.StreamingProgressChanged += Streamable_StreamingProgressChanged; - streamable.StreamingTimeRemaining += Streamable_StreamingTimeRemaining; - streamable.StreamingCompleted += Streamable_StreamingCompleted; - streamable.StreamingCompleted += OnStreamingCompletedClose; - - Disposed += UnsubscribeStreamable; - } - private void Subscribe(Processable processable) - { - UnsubscribeProcessable(this, null); - - processable.Begin += Processable_Begin; - processable.StatusUpdate += Processable_StatusUpdate; - processable.Completed += Processable_Completed; - - //The form is created on Processable.Begin and we - //dispose of it on Processable.Completed - processable.Completed += OnCompletedDispose; - - //Don't unsubscribe from Dispose because it fires when - //Streamable.StreamingCompleted closes the form, and - //the Processable events need to live past that event. - processable.Completed += UnsubscribeProcessable; - } - private void Subscribe(AudioDecodable audioDecodable) - { - UnsubscribeAudioDecodable(this, EventArgs.Empty); - - audioDecodable.RequestCoverArt += AudioDecodable_RequestCoverArt; - audioDecodable.TitleDiscovered += AudioDecodable_TitleDiscovered; - audioDecodable.AuthorsDiscovered += AudioDecodable_AuthorsDiscovered; - audioDecodable.NarratorsDiscovered += AudioDecodable_NarratorsDiscovered; - audioDecodable.CoverImageDiscovered += AudioDecodable_CoverImageDiscovered; - - Disposed += UnsubscribeAudioDecodable; - } - private void UnsubscribeStreamable(object sender, EventArgs e) - { - Disposed -= UnsubscribeStreamable; - - Streamable.StreamingBegin -= OnStreamingBeginShow; - Streamable.StreamingBegin -= Streamable_StreamingBegin; - Streamable.StreamingProgressChanged -= Streamable_StreamingProgressChanged; - Streamable.StreamingTimeRemaining -= Streamable_StreamingTimeRemaining; - Streamable.StreamingCompleted -= Streamable_StreamingCompleted; - Streamable.StreamingCompleted -= OnStreamingCompletedClose; - } - private void UnsubscribeProcessable(object sender, LibraryBook e) - { - if (Streamable is not Processable processable) - return; - - processable.Completed -= UnsubscribeProcessable; - processable.Completed -= OnCompletedDispose; - processable.Completed -= Processable_Completed; - processable.StatusUpdate -= Processable_StatusUpdate; - processable.Begin -= Processable_Begin; - } - private void UnsubscribeAudioDecodable(object sender, EventArgs e) - { - if (Streamable is not AudioDecodable audioDecodable) - return; - - Disposed -= UnsubscribeAudioDecodable; - audioDecodable.RequestCoverArt -= AudioDecodable_RequestCoverArt; - audioDecodable.TitleDiscovered -= AudioDecodable_TitleDiscovered; - audioDecodable.AuthorsDiscovered -= AudioDecodable_AuthorsDiscovered; - audioDecodable.NarratorsDiscovered -= AudioDecodable_NarratorsDiscovered; - audioDecodable.CoverImageDiscovered -= AudioDecodable_CoverImageDiscovered; - - audioDecodable.Cancel(); - } - #endregion - - #region Form creation and disposal handling - - /// - /// If the form was shown using Show (not ShowDialog), Form.Close calls Form.Dispose - /// - private void OnStreamingCompletedClose(object sender, string completedString) => this.UIThreadAsync(Close); - private void OnCompletedDispose(object sender, LibraryBook e) => this.UIThreadAsync(Dispose); - - /// - /// If StreamingBegin is fired from a worker thread, the window will be created on that - /// worker thread. We need to make certain that we show the window on the UI thread (same - /// thread that created form), otherwise the renderer will be on a worker thread which - /// could cause it to freeze. Form.BeginInvoke won't work until the form is created - /// (ie. shown) because Control doesn't get a window handle until it is Shown. - /// - private void OnStreamingBeginShow(object sender, string beginString) => Invoker.UIThreadAsync(Show); - - #endregion - - #region Streamable event handlers - public virtual void Streamable_StreamingBegin(object sender, string beginString) { } - public virtual void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress) { } - public virtual void Streamable_StreamingTimeRemaining(object sender, TimeSpan timeRemaining) { } - public virtual void Streamable_StreamingCompleted(object sender, string completedString) { } - - #endregion - - #region Processable event handlers - public virtual void Processable_Begin(object sender, LibraryBook libraryBook) { } - public virtual void Processable_StatusUpdate(object sender, string statusUpdate) { } - public virtual void Processable_Completed(object sender, LibraryBook libraryBook) { } - - #endregion - - #region AudioDecodable event handlers - public virtual void AudioDecodable_TitleDiscovered(object sender, string title) { } - public virtual void AudioDecodable_AuthorsDiscovered(object sender, string authors) { } - public virtual void AudioDecodable_NarratorsDiscovered(object sender, string narrators) { } - - public virtual void AudioDecodable_CoverImageDiscovered(object sender, byte[] coverArt) { } - public virtual void AudioDecodable_RequestCoverArt(object sender, Action setCoverArtDelegate) { } - #endregion - } -} diff --git a/Source/LibationWinForms/BookLiberation/DownloadForm.cs b/Source/LibationWinForms/BookLiberation/DownloadForm.cs index 3a307476..77d6058f 100644 --- a/Source/LibationWinForms/BookLiberation/DownloadForm.cs +++ b/Source/LibationWinForms/BookLiberation/DownloadForm.cs @@ -2,30 +2,48 @@ using System.Windows.Forms; using Dinah.Core.Net.Http; using Dinah.Core.Threading; -using LibationWinForms.BookLiberation.BaseForms; +using FileLiberator; namespace LibationWinForms.BookLiberation { - public partial class DownloadForm : LiberationBaseForm + public partial class DownloadForm : Form { + protected Streamable Streamable { get; private set; } + protected LogMe LogMe { get; private set; } + private SynchronizeInvoker Invoker { get; init; } + public DownloadForm() { + //SynchronizationContext.Current will be null until the process contains a Form. + //If this is the first form created, it will not exist until after execution + //reaches inside the constructor (after base class has been initialized). + Invoker = new SynchronizeInvoker(); InitializeComponent(); + this.SetLibationIcon(); progressLbl.Text = ""; filenameLbl.Text = ""; } + public void RegisterFileLiberator(Streamable streamable, LogMe logMe = null) + { + if (streamable is null) return; + streamable.StreamingBegin += Streamable_StreamingBegin; + streamable.StreamingProgressChanged += Streamable_StreamingProgressChanged; + streamable.StreamingCompleted += (_, _) => this.UIThreadAsync(Close); + Streamable = streamable; + LogMe = logMe; + } + #region Streamable event handler overrides - public override void Streamable_StreamingBegin(object sender, string beginString) + public void Streamable_StreamingBegin(object sender, string beginString) { - base.Streamable_StreamingBegin(sender, beginString); + Invoker.UIThreadAsync(Show); filenameLbl.UIThreadAsync(() => filenameLbl.Text = beginString); } - public override void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress) + public void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress) { - base.Streamable_StreamingProgressChanged(sender, downloadProgress); // this won't happen with download file. it will happen with download string if (!downloadProgress.TotalBytesToReceive.HasValue || downloadProgress.TotalBytesToReceive.Value <= 0) return; From 133dbb74711293a40c8e33ec8b640021b78e84d2 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 15:11:21 -0600 Subject: [PATCH 21/22] Update Dinah --- Source/FileManager/FileManager.csproj | 2 +- .../grid/FilterableSortableBindingList.cs | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Source/FileManager/FileManager.csproj b/Source/FileManager/FileManager.csproj index acf472a9..f906047a 100644 --- a/Source/FileManager/FileManager.csproj +++ b/Source/FileManager/FileManager.csproj @@ -5,7 +5,7 @@ - + diff --git a/Source/LibationWinForms/grid/FilterableSortableBindingList.cs b/Source/LibationWinForms/grid/FilterableSortableBindingList.cs index 3adfc663..5cee30dd 100644 --- a/Source/LibationWinForms/grid/FilterableSortableBindingList.cs +++ b/Source/LibationWinForms/grid/FilterableSortableBindingList.cs @@ -26,14 +26,7 @@ namespace LibationWinForms /// private readonly List FilterRemoved = new(); private string FilterString; - private Action Sort; - public FilterableSortableBindingList(IEnumerable enumeration) : base(enumeration) - { - //This is only necessary because SortableBindingList doesn't expose Sort() - //You should make SortableBindingList.Sort protected and remove reflection - var method = typeof(SortableBindingList).GetMethod("Sort", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - Sort = method.CreateDelegate(this); - } + public FilterableSortableBindingList(IEnumerable enumeration) : base(enumeration) { } public bool SupportsFiltering => true; public string Filter { get => FilterString; set => ApplyFilter(value); } From 789b9207b5c8c935e17c50c0e4e19d352f23cc78 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 May 2022 15:49:02 -0600 Subject: [PATCH 22/22] Use that fancy patterm matching --- .../ProcessQueue/ProcessQueueControl.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index 59442a1c..98b7c01a 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -270,9 +270,9 @@ namespace LibationWinForms.ProcessQueue syncContext.Send(_ => { Panels[i].SuspendLayout(); - if (propertyName is null || propertyName == nameof(proc.Cover)) + if (propertyName is null or nameof(proc.Cover)) Panels[i].SetCover(proc.Cover); - if (propertyName is null || propertyName == nameof(proc.BookText)) + if (propertyName is null or nameof(proc.BookText)) Panels[i].SetBookInfo(proc.BookText); if (proc.Result != ProcessBookResult.None) @@ -281,14 +281,15 @@ namespace LibationWinForms.ProcessQueue return; } - if (propertyName is null || propertyName == nameof(proc.Status)) + if (propertyName is null or nameof(proc.Status)) Panels[i].SetStatus(proc.Status); - if (propertyName is null || propertyName == nameof(proc.Progress)) + if (propertyName is null or nameof(proc.Progress)) Panels[i].SetProgrss(proc.Progress); - if (propertyName is null || propertyName == nameof(proc.TimeRemaining)) + if (propertyName is null or nameof(proc.TimeRemaining)) Panels[i].SetRemainingTime(proc.TimeRemaining); Panels[i].ResumeLayout(); - }, null); + }, + null); } private void UpdateAllControls()