improve startup time

This commit is contained in:
Mbucari 2023-07-08 13:53:01 -06:00
parent 9720a573c7
commit 5d5e3a6671
12 changed files with 157 additions and 60 deletions

View File

@ -1,7 +1,10 @@
using ApplicationServices; using ApplicationServices;
using Avalonia.Threading; using Avalonia.Threading;
using DataLayer;
using LibationFileManager; using LibationFileManager;
using ReactiveUI; using ReactiveUI;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LibationAvalonia.ViewModels namespace LibationAvalonia.ViewModels
@ -41,16 +44,17 @@ namespace LibationAvalonia.ViewModels
private void Configure_BackupCounts() private void Configure_BackupCounts()
{ {
MainWindow.Loaded += setBackupCounts; MainWindow.LibraryLoaded += (_, e) => setBackupCounts(e.Where(l => !l.Book.IsEpisodeParent()));
LibraryCommands.LibrarySizeChanged += setBackupCounts; LibraryCommands.LibrarySizeChanged += (_,_) => setBackupCounts();
LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts; LibraryCommands.BookUserDefinedItemCommitted += (_, _) => setBackupCounts();
} }
private async void setBackupCounts(object _, object __) private async void setBackupCounts(IEnumerable<LibraryBook> libraryBooks = null)
{ {
if (updateCountsTask?.IsCompleted ?? true) if (updateCountsTask?.IsCompleted ?? true)
{ {
updateCountsTask = Task.Run(() => LibraryCommands.GetCounts()); libraryBooks ??= DbContexts.GetLibrary_Flat_NoTracking();
updateCountsTask = Task.Run(() => LibraryCommands.GetCounts(libraryBooks));
var stats = await updateCountsTask; var stats = await updateCountsTask;
await Dispatcher.UIThread.InvokeAsync(() => LibraryStats = stats); await Dispatcher.UIThread.InvokeAsync(() => LibraryStats = stats);

View File

@ -91,37 +91,21 @@ namespace LibationAvalonia.ViewModels
#region Display Functions #region Display Functions
internal void BindToGrid(List<LibraryBook> dbBooks) internal async Task BindToGridAsync(List<LibraryBook> dbBooks)
{ {
GridEntries = new(SOURCE) { Filter = CollectionFilter }; GridEntries = new(SOURCE) { Filter = CollectionFilter };
var geList = dbBooks var geList = await LibraryBookEntry<AvaloniaEntryStatus>.GetAllProductsAsync(dbBooks);
.Where(lb => lb.Book.IsProduct())
.Select(b => new LibraryBookEntry<AvaloniaEntryStatus>(b))
.ToList<IGridEntry>();
var episodes = dbBooks.Where(lb => lb.Book.IsEpisodeChild()).ToList(); var seriesEntries = await SeriesEntry<AvaloniaEntryStatus>.GetAllSeriesEntriesAsync(dbBooks);
var seriesBooks = dbBooks.Where(lb => lb.Book.IsEpisodeParent()).ToList(); SOURCE.AddRange(geList.Concat(seriesEntries).OrderDescending(new RowComparer(null)));
foreach (var parent in seriesBooks)
{
var seriesEpisodes = episodes.FindChildren(parent);
if (!seriesEpisodes.Any()) continue;
var seriesEntry = new SeriesEntry<AvaloniaEntryStatus>(parent, seriesEpisodes);
seriesEntry.Liberate.Expanded = false;
geList.Add(seriesEntry);
}
//Create the filtered-in list before adding entries to avoid a refresh //Create the filtered-in list before adding entries to avoid a refresh
FilteredInGridEntries = geList.Union(geList.OfType<ISeriesEntry>().SelectMany(s => s.Children)).FilterEntries(FilterString); FilteredInGridEntries = geList.Union(seriesEntries.SelectMany(s => s.Children)).FilterEntries(FilterString);
SOURCE.AddRange(geList.OrderDescending(new RowComparer(null)));
//Add all children beneath their parent //Add all children beneath their parent
foreach (var series in SOURCE.OfType<ISeriesEntry>().ToList()) foreach (var series in seriesEntries)
{ {
var seriesIndex = SOURCE.IndexOf(series); var seriesIndex = SOURCE.IndexOf(series);
foreach (var child in series.Children) foreach (var child in series.Children)

View File

@ -61,7 +61,7 @@ namespace LibationAvalonia.Views
if (QuickFilters.UseDefault) if (QuickFilters.UseDefault)
await ViewModel.PerformFilter(QuickFilters.Filters.FirstOrDefault()); await ViewModel.PerformFilter(QuickFilters.Filters.FirstOrDefault());
ViewModel.ProductsDisplay.BindToGrid(dbBooks); await ViewModel.ProductsDisplay.BindToGridAsync(dbBooks);
} }
private void selectAndFocusSearchBox() private void selectAndFocusSearchBox()

View File

@ -51,7 +51,7 @@ namespace LibationAvalonia.Views
catch { sampleEntries = new(); } catch { sampleEntries = new(); }
var pdvm = new ProductsDisplayViewModel(); var pdvm = new ProductsDisplayViewModel();
pdvm.BindToGrid(sampleEntries); _ = pdvm.BindToGridAsync(sampleEntries);
DataContext = pdvm; DataContext = pdvm;
return; return;

View File

@ -1,6 +1,10 @@
using DataLayer; using DataLayer;
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
namespace LibationUiBase.GridView namespace LibationUiBase.GridView
{ {
@ -29,6 +33,39 @@ namespace LibationUiBase.GridView
LoadCover(); LoadCover();
} }
public static async Task<List<IGridEntry>> GetAllProductsAsync(IEnumerable<LibraryBook> libraryBooks)
{
var products = libraryBooks.Where(lb => lb.Book.IsProduct()).ToArray();
int parallelism = int.Max(1, Environment.ProcessorCount - 1);
(int numPer, int rem) = int.DivRem(products.Length, parallelism);
if (rem != 0) numPer++;
var tasks = new Task<IGridEntry[]>[parallelism];
var syncContext = SynchronizationContext.Current;
for (int i = 0; i < parallelism; i++)
{
int start = i * numPer;
tasks[i] = Task.Run(() =>
{
SynchronizationContext.SetSynchronizationContext(syncContext);
int length = int.Min(numPer, products.Length - start);
var result = new IGridEntry[length];
for (int j = 0; j < length; j++)
result[j] = new LibraryBookEntry<TStatus>(products[start + j]);
return result;
});
}
return (await Task.WhenAll(tasks)).SelectMany(a => a).ToList();
}
protected override string GetBookTags() => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); protected override string GetBookTags() => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
} }
} }

View File

@ -1,7 +1,11 @@
using DataLayer; using DataLayer;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace LibationUiBase.GridView namespace LibationUiBase.GridView
{ {
@ -54,6 +58,60 @@ namespace LibationUiBase.GridView
LoadCover(); LoadCover();
} }
public static async Task<List<ISeriesEntry>> GetAllSeriesEntriesAsync(IEnumerable<LibraryBook> libraryBooks)
{
var seriesBooks = libraryBooks.Where(lb => lb.Book.IsEpisodeParent()).ToArray();
var allEpisodes = libraryBooks.Where(lb => lb.Book.IsEpisodeChild()).ToArray();
int parallelism = int.Max(1, Environment.ProcessorCount - 1);
var tasks = new Task[parallelism];
var syncContext = SynchronizationContext.Current;
var q = new BlockingCollection<(int, LibraryBook episode)>();
var seriesEntries = new ISeriesEntry[seriesBooks.Length];
var seriesEpisodes = new ConcurrentBag<ILibraryBookEntry>[seriesBooks.Length];
for (int i = 0; i < parallelism; i++)
{
tasks[i] = Task.Run(() =>
{
SynchronizationContext.SetSynchronizationContext(syncContext);
while (q.TryTake(out var entry, -1))
{
var parent = seriesEntries[entry.Item1];
var episodeBag = seriesEpisodes[entry.Item1];
episodeBag.Add(new LibraryBookEntry<TStatus>(entry.episode, parent));
}
});
}
for (int i = 0; i <seriesBooks.Length; i++)
{
var series = seriesBooks[i];
seriesEntries[i] = new SeriesEntry<TStatus>(series, Enumerable.Empty<LibraryBook>());
seriesEpisodes[i] = new ConcurrentBag<ILibraryBookEntry>();
foreach (var ep in allEpisodes.FindChildren(series))
q.Add((i, ep));
}
q.CompleteAdding();
await Task.WhenAll(tasks);
for (int i = 0; i < seriesBooks.Length; i++)
{
var series = seriesEntries[i];
series.Children.AddRange(seriesEpisodes[i].OrderByDescending(c => c.SeriesOrder));
series.UpdateLibraryBook(series.LibraryBook);
}
return seriesEntries.Where(s => s.Children.Count != 0).ToList();
}
public void RemoveChild(ILibraryBookEntry lbe) public void RemoveChild(ILibraryBookEntry lbe)
{ {
Children.Remove(lbe); Children.Remove(lbe);

View File

@ -1,6 +1,8 @@
using ApplicationServices; using ApplicationServices;
using DataLayer;
using Dinah.Core; using Dinah.Core;
using Dinah.Core.Threading; using Dinah.Core.Threading;
using System.Collections.Generic;
namespace LibationWinForms namespace LibationWinForms
{ {
@ -14,7 +16,6 @@ namespace LibationWinForms
beginBookBackupsToolStripMenuItem.Format(0); beginBookBackupsToolStripMenuItem.Format(0);
beginPdfBackupsToolStripMenuItem.Format(0); beginPdfBackupsToolStripMenuItem.Format(0);
Load += setBackupCounts;
LibraryCommands.LibrarySizeChanged += setBackupCounts; LibraryCommands.LibrarySizeChanged += setBackupCounts;
LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts; LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts;
@ -40,7 +41,11 @@ namespace LibationWinForms
while (runBackupCountsAgain) while (runBackupCountsAgain)
{ {
runBackupCountsAgain = false; runBackupCountsAgain = false;
e.Result = LibraryCommands.GetCounts();
if (e.Argument is not IEnumerable<LibraryBook> lbs)
lbs = DbContexts.GetLibrary_Flat_NoTracking();
e.Result = LibraryCommands.GetCounts(lbs);
} }
} }

View File

@ -4,10 +4,8 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using ApplicationServices; using ApplicationServices;
using Dinah.Core; using DataLayer;
using Dinah.Core.Threading;
using LibationFileManager; using LibationFileManager;
using LibationWinForms.Dialogs;
namespace LibationWinForms namespace LibationWinForms
{ {
@ -57,8 +55,7 @@ namespace LibationWinForms
// Configure_Grid(); // since it's just this, can keep here. If it needs more, then give grid it's own 'partial class Form1' // Configure_Grid(); // since it's just this, can keep here. If it needs more, then give grid it's own 'partial class Form1'
{ {
this.Load += (_, __) => productsDisplay.Display(); LibraryCommands.LibrarySizeChanged += (_, __) => Invoke(productsDisplay.DisplayAsync);
LibraryCommands.LibrarySizeChanged += (_, __) => this.UIThreadAsync(() => productsDisplay.Display());
} }
Shown += Form1_Shown; Shown += Form1_Shown;
} }
@ -78,6 +75,13 @@ namespace LibationWinForms
} }
} }
public async Task InitLibraryAsync(List<LibraryBook> libraryBooks)
{
runBackupCountsAgain = true;
updateCountsBw.RunWorkerAsync(libraryBooks.Where(b => !b.Book.IsEpisodeParent()));
await productsDisplay.DisplayAsync(libraryBooks);
}
private void Form1_Load(object sender, EventArgs e) private void Form1_Load(object sender, EventArgs e)
{ {
if (this.DesignMode) if (this.DesignMode)

View File

@ -30,7 +30,6 @@ namespace LibationWinForms.GridView
{ {
SearchEngineCommands.SearchEngineUpdated += SearchEngineCommands_SearchEngineUpdated; SearchEngineCommands.SearchEngineUpdated += SearchEngineCommands_SearchEngineUpdated;
ListChanged += GridEntryBindingList_ListChanged; ListChanged += GridEntryBindingList_ListChanged;
refreshEntries();
} }
/// <returns>All items in the list, including those filtered out.</returns> /// <returns>All items in the list, including those filtered out.</returns>

View File

@ -306,22 +306,22 @@ namespace LibationWinForms.GridView
#region UI display functions #region UI display functions
public void Display() public async Task DisplayAsync(List<LibraryBook> libraryBooks = null)
{ {
try try
{ {
// don't return early if lib size == 0. this will not update correctly if all books are removed // don't return early if lib size == 0. this will not update correctly if all books are removed
var lib = DbContexts.GetLibrary_Flat_NoTracking(includeParents: true); libraryBooks ??= DbContexts.GetLibrary_Flat_NoTracking(includeParents: true);
if (!hasBeenDisplayed) if (!hasBeenDisplayed)
{ {
// bind // bind
productsGrid.BindToGrid(lib); await productsGrid.BindToGridAsync(libraryBooks);
hasBeenDisplayed = true; hasBeenDisplayed = true;
InitialLoaded?.Invoke(this, new()); InitialLoaded?.Invoke(this, new());
} }
else else
productsGrid.UpdateGrid(lib); productsGrid.UpdateGrid(libraryBooks);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -5,8 +5,11 @@ using LibationUiBase.GridView;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace LibationWinForms.GridView namespace LibationWinForms.GridView
@ -160,27 +163,23 @@ namespace LibationWinForms.GridView
} }
} }
internal void BindToGrid(List<LibraryBook> dbBooks) internal async Task BindToGridAsync(List<LibraryBook> dbBooks)
{ {
var geList = dbBooks var geList = await LibraryBookEntry<WinFormsEntryStatus>.GetAllProductsAsync(dbBooks);
.Where(lb => lb.Book.IsProduct())
.Select(b => new LibraryBookEntry<WinFormsEntryStatus>(b))
.ToList<IGridEntry>();
var episodes = dbBooks.Where(lb => lb.Book.IsEpisodeChild()); var seriesEntries = await SeriesEntry<WinFormsEntryStatus>.GetAllSeriesEntriesAsync(dbBooks);
var seriesBooks = dbBooks.Where(lb => lb.Book.IsEpisodeParent()).ToList(); geList.AddRange(seriesEntries);
//Sort descending by date (default sort property)
var comparer = new RowComparer();
geList.Sort((a, b) => comparer.Compare(b, a));
foreach (var parent in seriesBooks) //Add all children beneath their parent
foreach (var series in seriesEntries)
{ {
var seriesEpisodes = episodes.FindChildren(parent); var seriesIndex = geList.IndexOf(series);
foreach (var child in series.Children)
if (!seriesEpisodes.Any()) continue; geList.Insert(++seriesIndex, child);
var seriesEntry = new SeriesEntry<WinFormsEntryStatus>(parent, seriesEpisodes);
geList.Add(seriesEntry);
geList.AddRange(seriesEntry.Children);
} }
bindingList = new GridEntryBindingList(geList); bindingList = new GridEntryBindingList(geList);

View File

@ -2,12 +2,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using ApplicationServices;
using AppScaffolding; using AppScaffolding;
using Dinah.Core; using DataLayer;
using LibationFileManager; using LibationFileManager;
using LibationWinForms.Dialogs; using LibationWinForms.Dialogs;
using Serilog;
namespace LibationWinForms namespace LibationWinForms
{ {
@ -20,6 +21,7 @@ namespace LibationWinForms
[STAThread] [STAThread]
static void Main() static void Main()
{ {
Task<List<LibraryBook>> libraryLoadTask;
try try
{ {
//// Uncomment to see Console. Must be called before anything writes to Console. //// Uncomment to see Console. Must be called before anything writes to Console.
@ -48,6 +50,9 @@ namespace LibationWinForms
// migrations which require Forms or are long-running // migrations which require Forms or are long-running
RunWindowsOnlyMigrations(config); RunWindowsOnlyMigrations(config);
//start loading the db as soon as possible
libraryLoadTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
MessageBoxLib.VerboseLoggingWarning_ShowIfTrue(); MessageBoxLib.VerboseLoggingWarning_ShowIfTrue();
// logging is init'd here // logging is init'd here
@ -71,7 +76,9 @@ namespace LibationWinForms
// global exception handling (ShowAdminAlert) attempts to use logging. only call it after logging has been init'd // global exception handling (ShowAdminAlert) attempts to use logging. only call it after logging has been init'd
postLoggingGlobalExceptionHandling(); postLoggingGlobalExceptionHandling();
Application.Run(new Form1()); var form1 = new Form1();
form1.Load += async (_, _) => await form1.InitLibraryAsync(await libraryLoadTask);
Application.Run(form1);
} }
private static void RunInstaller(Configuration config) private static void RunInstaller(Configuration config)