From 9d2164962292104aa9f33bdaef74fb8871208549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 10 Nov 2020 17:13:45 +0800 Subject: [PATCH 01/62] Change UI rendering logic Co-authored-by: Bao-Qian --- Flow.Launcher/ResultListBox.xaml | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 24 +++++- Flow.Launcher/ViewModel/ResultsForUpdate.cs | 36 +++++++++ Flow.Launcher/ViewModel/ResultsViewModel.cs | 85 ++++++++------------- 4 files changed, 90 insertions(+), 57 deletions(-) create mode 100644 Flow.Launcher/ViewModel/ResultsForUpdate.cs diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 3280dc457a1..60d94a37b09 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -42,7 +42,7 @@ + Source="{Binding Image}" /> diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f18b7402279..ac2cc79d5d2 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -21,6 +21,7 @@ using Flow.Launcher.Storage; using System.Windows.Media; using Flow.Launcher.Infrastructure.Image; +using System.Collections.Concurrent; namespace Flow.Launcher.ViewModel { @@ -47,6 +48,8 @@ public class MainViewModel : BaseModel, ISavable private bool _saved; private readonly Internationalization _translator = InternationalizationManager.Instance; + private BlockingCollection _resultsUpdateQueue; + #endregion @@ -76,6 +79,8 @@ public MainViewModel(Settings settings) InitializeKeyCommands(); RegisterResultsUpdatedEvent(); + RegisterResultUpdate(); + SetHotkey(_settings.Hotkey, OnHotkey); SetCustomPluginHotkey(); SetOpenResultModifiers(); @@ -91,12 +96,27 @@ private void RegisterResultsUpdatedEvent() Task.Run(() => { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - UpdateResultView(e.Results, pair.Metadata, e.Query); + _resultsUpdateQueue.Add(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); }, _updateToken); }; } } + private void RegisterResultUpdate() + { + _resultsUpdateQueue = new BlockingCollection(); + + Task.Run(() => + { + while (true) + { + var resultToUpdate = _resultsUpdateQueue.Take(); + if (!resultToUpdate.Token.IsCancellationRequested) + UpdateResultView(resultToUpdate.Results, resultToUpdate.Metadata, resultToUpdate.Query); + } + }); + } + private void InitializeKeyCommands() { @@ -415,7 +435,7 @@ private void QueryResults() if (!plugin.Metadata.Disabled) { var results = PluginManager.QueryForPlugin(plugin, query); - UpdateResultView(results, plugin.Metadata, query); + _resultsUpdateQueue.Add(new ResultsForUpdate(results, plugin.Metadata, query, _updateToken)); } }); } diff --git a/Flow.Launcher/ViewModel/ResultsForUpdate.cs b/Flow.Launcher/ViewModel/ResultsForUpdate.cs new file mode 100644 index 00000000000..29f6262853d --- /dev/null +++ b/Flow.Launcher/ViewModel/ResultsForUpdate.cs @@ -0,0 +1,36 @@ +using Flow.Launcher.Plugin; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Flow.Launcher.ViewModel +{ + class ResultsForUpdate + { + public List Results { get; } + + public PluginMetadata Metadata { get; } + public string ID { get; } + + public Query Query { get; } + public CancellationToken Token { get; } + + public ResultsForUpdate(List results, string resultID, CancellationToken token) + { + Results = results; + ID = resultID; + Token = token; + } + + + public ResultsForUpdate(List results, PluginMetadata metadata, Query query, CancellationToken token) + { + Results = results; + Metadata = metadata; + Query = query; + Token = token; + ID = metadata.ID; + } + } +} diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d3085418062..ac435c494c6 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -17,7 +17,6 @@ public class ResultsViewModel : BaseModel public ResultCollection Results { get; } - private readonly object _addResultsLock = new object(); private readonly object _collectionLock = new object(); private readonly Settings _settings; private int MaxResults => _settings?.MaxResultsToShow ?? 6; @@ -134,70 +133,37 @@ public void RemoveResultsFor(PluginMetadata metadata) /// public void AddResults(List newRawResults, string resultId) { - lock (_addResultsLock) - { - var newResults = NewResults(newRawResults, resultId); + var newResults = NewResults(newRawResults, resultId); - // update UI in one run, so it can avoid UI flickering - Results.Update(newResults); + // update UI in one run, so it can avoid UI flickering + Results.Update(newResults); - if (Results.Count > 0) - { - Margin = new Thickness { Top = 8 }; - SelectedIndex = 0; - } - else - { - Margin = new Thickness { Top = 0 }; - } + if (Results.Count > 0) + { + Margin = new Thickness { Top = 8 }; + SelectedIndex = 0; + } + else + { + Margin = new Thickness { Top = 0 }; } } private List NewResults(List newRawResults, string resultId) { - var results = Results.ToList(); - var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList(); - var oldResults = results.Where(r => r.Result.PluginID == resultId).ToList(); - - // Find the same results in A (old results) and B (new newResults) - var sameResults = oldResults - .Where(t1 => newResults.Any(x => x.Result.Equals(t1.Result))) - .ToList(); - - // remove result of relative complement of B in A - foreach (var result in oldResults.Except(sameResults)) - { - results.Remove(result); - } - - // update result with B's score and index position - foreach (var sameResult in sameResults) - { - int oldIndex = results.IndexOf(sameResult); - int oldScore = results[oldIndex].Result.Score; - var newResult = newResults[newResults.IndexOf(sameResult)]; - int newScore = newResult.Result.Score; - if (newScore != oldScore) - { - var oldResult = results[oldIndex]; + if (newRawResults.Count == 0) + return Results.ToList(); - oldResult.Result.Score = newScore; - oldResult.Result.OriginQuery = newResult.Result.OriginQuery; + var results = Results as IEnumerable; - results.RemoveAt(oldIndex); - int newIndex = InsertIndexOf(newScore, results); - results.Insert(newIndex, oldResult); - } - } + var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList(); - // insert result in relative complement of A in B - foreach (var result in newResults.Except(sameResults)) - { - int newIndex = InsertIndexOf(result.Result.Score, results); - results.Insert(newIndex, result); - } - return results; + return results.Where(r => r.Result.PluginID != resultId) + .Concat(newResults) + .OrderByDescending(r => r.Result.Score) + .Take(MaxResults * 2) + .ToList(); } #endregion @@ -254,6 +220,17 @@ public void RemoveAll(Predicate predicate) /// public void Update(List newItems) { + ClearItems(); + + foreach (var item in newItems) + { + Add(item); + } + + + + return; + int newCount = newItems.Count; int oldCount = Items.Count; int location = newCount > oldCount ? oldCount : newCount; From 0e0f24f635bcbe4723692fe649c6ae475da34786 Mon Sep 17 00:00:00 2001 From: Qian Bao Date: Fri, 23 Oct 2020 10:45:22 +0800 Subject: [PATCH 02/62] Lazy Load Image --- Flow.Launcher/ResultListBox.xaml | 2 +- Flow.Launcher/ViewModel/ResultViewModel.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 60d94a37b09..072196605a6 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -42,7 +42,7 @@ + Source="{Binding Image.Value}" /> diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index a4fe2ede4fc..a64836285e4 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -18,6 +18,7 @@ public ResultViewModel(Result result, Settings settings) if (result != null) { Result = result; + Image = new Lazy(() => SetImage); } Settings = settings; @@ -36,8 +37,10 @@ public ResultViewModel(Result result, Settings settings) public string ShowSubTitleToolTip => string.IsNullOrEmpty(Result.SubTitleToolTip) ? Result.SubTitle : Result.SubTitleToolTip; + + public Lazy Image { get; set; } - public ImageSource Image + private ImageSource SetImage { get { @@ -75,6 +78,7 @@ public override bool Equals(object obj) } } + public override int GetHashCode() { return Result.GetHashCode(); From 52887aacf04e2f98de33e7caf21d2226924a0535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 11 Nov 2020 18:36:01 +0800 Subject: [PATCH 03/62] Batch process ui change with interval 50ms --- Flow.Launcher/ViewModel/MainViewModel.cs | 46 +++++++-- Flow.Launcher/ViewModel/ResultsForUpdate.cs | 2 +- Flow.Launcher/ViewModel/ResultsViewModel.cs | 107 ++++++++++---------- 3 files changed, 93 insertions(+), 62 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ac2cc79d5d2..0a1ef8d8141 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -106,13 +106,25 @@ private void RegisterResultUpdate() { _resultsUpdateQueue = new BlockingCollection(); - Task.Run(() => + Task.Run(async () => { while (true) { - var resultToUpdate = _resultsUpdateQueue.Take(); - if (!resultToUpdate.Token.IsCancellationRequested) - UpdateResultView(resultToUpdate.Results, resultToUpdate.Metadata, resultToUpdate.Query); + List queue = new List() { _resultsUpdateQueue.Take() }; + await Task.Delay(50); + + while (_resultsUpdateQueue.TryTake(out var resultsForUpdate)) + { + queue.Add(resultsForUpdate); + } + + //foreach (var update in queue) + //{ + // UpdateResultView(update.Results, update.Metadata, update.Query); + //} + + UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); + } }); } @@ -412,7 +424,7 @@ private void QueryResults() if (query != null) { // handle the exclusiveness of plugin using action keyword - RemoveOldQueryResults(query); + // RemoveOldQueryResults(query); _lastQuery = query; Task.Delay(200, currentCancellationToken).ContinueWith(_ => @@ -443,7 +455,7 @@ private void QueryResults() { // nothing to do here } - + // this should happen once after all queries are done so progress bar should continue // until the end of all querying @@ -459,6 +471,7 @@ private void QueryResults() { Results.Clear(); Results.Visbility = Visibility.Collapsed; + } } @@ -683,6 +696,23 @@ public void Save() } } + public void UpdateResultView(IEnumerable resultsForUpdates) + { + foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) + { + if (_topMostRecord.IsTopMost(result)) + { + result.Score = int.MaxValue; + } + else + { + result.Score += _userSelectedRecord.GetSelectedCount(result) * 5; + } + } + + Results.AddResults(resultsForUpdates); + } + /// /// To avoid deadlock, this method should not called from main thread /// @@ -705,10 +735,6 @@ public void UpdateResultView(List list, PluginMetadata metadata, Query o Results.AddResults(list, metadata.ID); } - if (Results.Visbility != Visibility.Visible && list.Count > 0) - { - Results.Visbility = Visibility.Visible; - } } #endregion diff --git a/Flow.Launcher/ViewModel/ResultsForUpdate.cs b/Flow.Launcher/ViewModel/ResultsForUpdate.cs index 29f6262853d..2257d35b0d0 100644 --- a/Flow.Launcher/ViewModel/ResultsForUpdate.cs +++ b/Flow.Launcher/ViewModel/ResultsForUpdate.cs @@ -6,7 +6,7 @@ namespace Flow.Launcher.ViewModel { - class ResultsForUpdate + public class ResultsForUpdate { public List Results { get; } diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index ac435c494c6..d06a390c5e1 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -115,19 +116,20 @@ public void SelectFirstResult() public void Clear() { - Results.Clear(); + Results.RemoveAll(); } public void RemoveResultsExcept(PluginMetadata metadata) { - Results.RemoveAll(r => r.Result.PluginID != metadata.ID); + //Results.RemoveAll(r => r.Result.PluginID != metadata.ID); } public void RemoveResultsFor(PluginMetadata metadata) { - Results.RemoveAll(r => r.Result.PluginID == metadata.ID); + //Results.RemoveAll(r => r.Result.PluginID == metadata.ID); } + /// /// To avoid deadlock, this method should not called from main thread /// @@ -138,16 +140,44 @@ public void AddResults(List newRawResults, string resultId) // update UI in one run, so it can avoid UI flickering Results.Update(newResults); - if (Results.Count > 0) + if (Visbility != Visibility.Visible && Results.Count > 0) { Margin = new Thickness { Top = 8 }; SelectedIndex = 0; + Visbility = Visibility.Visible; } else { Margin = new Thickness { Top = 0 }; + Visbility = Visibility.Collapsed; } } + /// + /// To avoid deadlock, this method should not called from main thread + /// + public void AddResults(IEnumerable resultsForUpdates) + { + var newResults = NewResults(resultsForUpdates); + lock (_collectionLock) + { + Results.Update(newResults); + } + + switch (Visbility) + { + case Visibility.Collapsed when Results.Count > 0: + Margin = new Thickness { Top = 8 }; + SelectedIndex = 0; + Visbility = Visibility.Visible; + break; + case Visibility.Visible when Results.Count == 0: + Margin = new Thickness { Top = 0 }; + Visbility = Visibility.Collapsed; + break; + + } + } + private List NewResults(List newRawResults, string resultId) { @@ -165,8 +195,25 @@ private List NewResults(List newRawResults, string resu .Take(MaxResults * 2) .ToList(); } + + private List NewResults(IEnumerable resultsForUpdates) + { + if (!resultsForUpdates.Any()) + return Results.ToList(); + + var results = Results as IEnumerable; + + return results.Where(r => !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID)) + .Concat(resultsForUpdates + .SelectMany(u => u.Results) + .Select(r => new ResultViewModel(r, _settings))) + .OrderByDescending(rv => rv.Result.Score) + .Take(MaxResults * 2) + .ToList(); + } #endregion + #region FormattedText Dependency Property public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached( "FormattedText", @@ -198,20 +245,12 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP } #endregion - public class ResultCollection : ObservableCollection + public class ResultCollection : ObservableCollection, INotifyCollectionChanged { - - public void RemoveAll(Predicate predicate) + public event NotifyCollectionChangedEventHandler CollectionChanged; + public void RemoveAll() { - CheckReentrancy(); - - for (int i = Count - 1; i >= 0; i--) - { - if (predicate(this[i])) - { - RemoveAt(i); - } - } + ClearItems(); } /// @@ -226,44 +265,10 @@ public void Update(List newItems) { Add(item); } + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - - return; - int newCount = newItems.Count; - int oldCount = Items.Count; - int location = newCount > oldCount ? oldCount : newCount; - - for (int i = 0; i < location; i++) - { - ResultViewModel oldResult = this[i]; - ResultViewModel newResult = newItems[i]; - if (!oldResult.Equals(newResult)) - { // result is not the same update it in the current index - this[i] = newResult; - } - else if (oldResult.Result.Score != newResult.Result.Score) - { - this[i].Result.Score = newResult.Result.Score; - } - } - - - if (newCount >= oldCount) - { - for (int i = oldCount; i < newCount; i++) - { - Add(newItems[i]); - } - } - else - { - for (int i = oldCount - 1; i >= newCount; i--) - { - RemoveAt(i); - } - } } } } From ebddf19ba21beccd961c3ad245640053e50fda3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 11 Nov 2020 19:06:05 +0800 Subject: [PATCH 04/62] Fix not canceled token and adding comment Co-authored-by: bao-qian --- Flow.Launcher/ViewModel/MainViewModel.cs | 6 ++++-- Flow.Launcher/ViewModel/ResultsViewModel.cs | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 0a1ef8d8141..f7167477432 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -469,9 +469,9 @@ private void QueryResults() } else { + _updateSource?.Cancel(); Results.Clear(); Results.Visbility = Visibility.Collapsed; - } } @@ -695,7 +695,9 @@ public void Save() _saved = true; } } - + /// + /// To avoid deadlock, this method should not called from main thread + /// public void UpdateResultView(IEnumerable resultsForUpdates) { foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d06a390c5e1..635b20ec61e 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -174,7 +174,6 @@ public void AddResults(IEnumerable resultsForUpdates) Margin = new Thickness { Top = 0 }; Visbility = Visibility.Collapsed; break; - } } @@ -247,7 +246,7 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP public class ResultCollection : ObservableCollection, INotifyCollectionChanged { - public event NotifyCollectionChangedEventHandler CollectionChanged; + public override event NotifyCollectionChangedEventHandler CollectionChanged; public void RemoveAll() { ClearItems(); @@ -259,12 +258,16 @@ public void RemoveAll() /// public void Update(List newItems) { + + ClearItems(); foreach (var item in newItems) { Add(item); } + + // wpf use directx / double buffered already, so just reset all won't cause ui flickering CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); return; From 1b76da175fa210e77dfc178e7007ea7aa2bf5721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 12 Nov 2020 15:37:04 +0800 Subject: [PATCH 05/62] Add remove previous query result back to keep different action word distinct --- Flow.Launcher/ViewModel/MainViewModel.cs | 8 ++++---- Flow.Launcher/ViewModel/ResultsViewModel.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f7167477432..cd2913570c9 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -424,7 +424,7 @@ private void QueryResults() if (query != null) { // handle the exclusiveness of plugin using action keyword - // RemoveOldQueryResults(query); + RemoveOldQueryResults(query); _lastQuery = query; Task.Delay(200, currentCancellationToken).ContinueWith(_ => @@ -483,18 +483,18 @@ private void RemoveOldQueryResults(Query query) { if (!string.IsNullOrEmpty(keyword)) { - Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + Results.KeepResultsFor(PluginManager.NonGlobalPlugins[keyword].Metadata); } } else { if (string.IsNullOrEmpty(keyword)) { - Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); + Results.KeepResultsExcept(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); } else if (lastKeyword != keyword) { - Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + Results.KeepResultsFor(PluginManager.NonGlobalPlugins[keyword].Metadata); } } } diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 635b20ec61e..3b48c2d3253 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -119,14 +119,14 @@ public void Clear() Results.RemoveAll(); } - public void RemoveResultsExcept(PluginMetadata metadata) + public void KeepResultsFor(PluginMetadata metadata) { - //Results.RemoveAll(r => r.Result.PluginID != metadata.ID); + Results.Update(Results.Where(r => r.Result.PluginID == metadata.ID).ToList()); } - public void RemoveResultsFor(PluginMetadata metadata) + public void KeepResultsExcept(PluginMetadata metadata) { - //Results.RemoveAll(r => r.Result.PluginID == metadata.ID); + Results.Update(Results.Where(r => r.Result.PluginID != metadata.ID).ToList()); } From f51d2c57c75dd77fbf30e3c3a26922f906fed792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 12 Nov 2020 15:46:24 +0800 Subject: [PATCH 06/62] keep all result in result list view --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 3b48c2d3253..038cfb4e330 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -191,7 +191,6 @@ private List NewResults(List newRawResults, string resu return results.Where(r => r.Result.PluginID != resultId) .Concat(newResults) .OrderByDescending(r => r.Result.Score) - .Take(MaxResults * 2) .ToList(); } @@ -207,7 +206,6 @@ private List NewResults(IEnumerable resultsFo .SelectMany(u => u.Results) .Select(r => new ResultViewModel(r, _settings))) .OrderByDescending(rv => rv.Result.Score) - .Take(MaxResults * 2) .ToList(); } #endregion From 4b2abfc7082764325c9fcee407ef7a202cba84a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 13 Nov 2020 13:41:59 +0800 Subject: [PATCH 07/62] Reduce batch time to speed up view update change selected index update place to fix not first selection issue --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- Flow.Launcher/ViewModel/ResultsViewModel.cs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index cd2913570c9..48e621126e5 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -111,7 +111,7 @@ private void RegisterResultUpdate() while (true) { List queue = new List() { _resultsUpdateQueue.Take() }; - await Task.Delay(50); + await Task.Delay(30); while (_resultsUpdateQueue.TryTake(out var resultsForUpdate)) { diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 038cfb4e330..3e464afe1f4 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -158,6 +158,11 @@ public void AddResults(List newRawResults, string resultId) public void AddResults(IEnumerable resultsForUpdates) { var newResults = NewResults(resultsForUpdates); + + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf + // fix selected index flow + SelectedIndex = 0; + lock (_collectionLock) { Results.Update(newResults); @@ -167,7 +172,6 @@ public void AddResults(IEnumerable resultsForUpdates) { case Visibility.Collapsed when Results.Count > 0: Margin = new Thickness { Top = 8 }; - SelectedIndex = 0; Visbility = Visibility.Visible; break; case Visibility.Visible when Results.Count == 0: @@ -175,6 +179,7 @@ public void AddResults(IEnumerable resultsForUpdates) Visbility = Visibility.Collapsed; break; } + } From e625c192ee5b64acba3195a6f7e0b16f0d6f7800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 13 Nov 2020 19:49:11 +0800 Subject: [PATCH 08/62] remove unused code --- Flow.Launcher/ViewModel/MainViewModel.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 48e621126e5..c093495e314 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -118,11 +118,6 @@ private void RegisterResultUpdate() queue.Add(resultsForUpdate); } - //foreach (var update in queue) - //{ - // UpdateResultView(update.Results, update.Metadata, update.Query); - //} - UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); } From e34a52c74e778d06e1e538f1cd585c73142bf84e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 12:04:10 +0800 Subject: [PATCH 09/62] Don't change selected index unless collection is not empty --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 3e464afe1f4..d62e0d0c356 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -137,8 +137,16 @@ public void AddResults(List newRawResults, string resultId) { var newResults = NewResults(newRawResults, resultId); - // update UI in one run, so it can avoid UI flickering - Results.Update(newResults); + lock (_collectionLock) + { + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf + // fix selected index flow + if (Results.Count > 0) + SelectedIndex = 0; + + // update UI in one run, so it can avoid UI flickering + Results.Update(newResults); + } if (Visbility != Visibility.Visible && Results.Count > 0) { @@ -159,12 +167,14 @@ public void AddResults(IEnumerable resultsForUpdates) { var newResults = NewResults(resultsForUpdates); - // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf - // fix selected index flow - SelectedIndex = 0; - lock (_collectionLock) { + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf + // fix selected index flow + if (Results.Count > 0) + SelectedIndex = 0; + + Results.Update(newResults); } @@ -172,6 +182,7 @@ public void AddResults(IEnumerable resultsForUpdates) { case Visibility.Collapsed when Results.Count > 0: Margin = new Thickness { Top = 8 }; + SelectedIndex = 0; Visbility = Visibility.Visible; break; case Visibility.Visible when Results.Count == 0: From a4147f52c93d8ecd5af31f1b7e8e5130ff26693d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 12:36:29 +0800 Subject: [PATCH 10/62] Block Notifychange unless all the element is added into the observablecollection This should improve the response speed. --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 36 ++++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d62e0d0c356..6210dc54f2e 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -260,7 +260,30 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP public class ResultCollection : ObservableCollection, INotifyCollectionChanged { + private bool _suppressNotification = false; + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (!_suppressNotification) + base.OnCollectionChanged(e); + } + public override event NotifyCollectionChangedEventHandler CollectionChanged; + + public void AddRange(IEnumerable Items) + { + _suppressNotification = true; + + foreach (var item in Items) + { + Add(item); + } + + + // wpf use directx / double buffered already, so just reset all won't cause ui flickering + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + return; + + } public void RemoveAll() { ClearItems(); @@ -272,20 +295,9 @@ public void RemoveAll() /// public void Update(List newItems) { - - ClearItems(); - foreach (var item in newItems) - { - Add(item); - } - - // wpf use directx / double buffered already, so just reset all won't cause ui flickering - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - - return; - + AddRange(newItems); } } } From dab8c5da957b3d3ad2b34f65ee6c0dcb35b106d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 12:36:29 +0800 Subject: [PATCH 11/62] Block Notifychange unless all the element is added into the observablecollection This should improve the response speed. --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 37 ++++++++++++++------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d62e0d0c356..4acf71eddc9 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -260,7 +260,31 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP public class ResultCollection : ObservableCollection, INotifyCollectionChanged { + private bool _suppressNotification = false; + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (!_suppressNotification) + base.OnCollectionChanged(e); + } + public override event NotifyCollectionChangedEventHandler CollectionChanged; + + // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ + public void AddRange(IEnumerable Items) + { + _suppressNotification = true; + + foreach (var item in Items) + { + Add(item); + } + + + // wpf use directx / double buffered already, so just reset all won't cause ui flickering + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + return; + + } public void RemoveAll() { ClearItems(); @@ -272,20 +296,9 @@ public void RemoveAll() /// public void Update(List newItems) { - - ClearItems(); - foreach (var item in newItems) - { - Add(item); - } - - // wpf use directx / double buffered already, so just reset all won't cause ui flickering - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - - return; - + AddRange(newItems); } } } From 4cf143d435eeaabe61cfe64ade7e47bcbc352a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 15:07:56 +0800 Subject: [PATCH 12/62] Don't notify change until finishing up adding new result --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 4acf71eddc9..438b64d921d 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -296,7 +296,7 @@ public void RemoveAll() /// public void Update(List newItems) { - ClearItems(); + Clear(); AddRange(newItems); } From 3076e501bfe629e10f96532d4a957187c7a3bb01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 17:09:37 +0800 Subject: [PATCH 13/62] add null check when processing result update --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 438b64d921d..5d6887a3fd5 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -217,10 +217,9 @@ private List NewResults(IEnumerable resultsFo var results = Results as IEnumerable; - return results.Where(r => !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID)) - .Concat(resultsForUpdates - .SelectMany(u => u.Results) - .Select(r => new ResultViewModel(r, _settings))) + return results.Where(r => r != null && !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID)) + .Concat( + resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings))) .OrderByDescending(rv => rv.Result.Score) .ToList(); } From 7dc247333ce6fece537e99912f7210b4f453cc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 17:20:48 +0800 Subject: [PATCH 14/62] remove check of empty of Results when change selected index --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 5d6887a3fd5..1db6ac62499 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -171,9 +171,7 @@ public void AddResults(IEnumerable resultsForUpdates) { // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow - if (Results.Count > 0) - SelectedIndex = 0; - + SelectedIndex = 0; Results.Update(newResults); } From c89ee33c5b6c72fab2aee25f6584b83c776dec03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 15 Nov 2020 20:39:02 +0800 Subject: [PATCH 15/62] Use Virtualizing StackPanel instead of GridView to greatly reduce the loading resources. --- Flow.Launcher/ResultListBox.xaml | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 072196605a6..350ef018e12 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -16,6 +16,7 @@ Style="{DynamicResource BaseListboxStyle}" Focusable="False" KeyboardNavigation.DirectionalNavigation="Cycle" SelectionMode="Single" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Standard" + ScrollViewer.IsDeferredScrollingEnabled="True" SelectionChanged="OnSelectionChanged" IsSynchronizedWithCurrentItem="True" PreviewMouseDown="ListBox_PreviewMouseDown"> @@ -29,26 +30,20 @@ - - + + - - - - - - + - + - + @@ -60,7 +55,7 @@ - + @@ -81,7 +76,7 @@ - + From dc982e277f3c7beff59a71675cde74853e7e0420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 15 Nov 2020 20:45:00 +0800 Subject: [PATCH 16/62] Use a default image to list when the image hasn't been loaded in cache. Co-authored-by: Bao-Qian --- .../Image/ImageCache.cs | 13 +++++---- .../Image/ImageLoader.cs | 11 +++++++- Flow.Launcher/ViewModel/ResultViewModel.cs | 28 +++++++++++++------ Flow.Launcher/ViewModel/ResultsViewModel.cs | 3 +- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 80c6684f55c..7482ac1cfec 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -26,7 +26,7 @@ public class ImageCache private const int MaxCached = 50; public ConcurrentDictionary Data { get; private set; } = new ConcurrentDictionary(); private const int permissibleFactor = 2; - + public void Initialization(Dictionary usage) { foreach (var key in usage.Keys) @@ -44,14 +44,14 @@ public ImageSource this[string path] value.usage++; return value.imageSource; } - + return null; } set { Data.AddOrUpdate( - path, - new ImageUsage(0, value), + path, + new ImageUsage(0, value), (k, v) => { v.imageSource = value; @@ -67,7 +67,8 @@ public ImageSource this[string path] // To delete the images from the data dictionary based on the resizing of the Usage Dictionary. - foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) + foreach (var key in Data.Where(x => x.Key != Constant.MissingImgIcon) + .OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) { if (!(key.Equals(Constant.ErrorIcon) || key.Equals(Constant.DefaultIcon))) { @@ -80,7 +81,7 @@ public ImageSource this[string path] public bool ContainsKey(string key) { - var contains = Data.ContainsKey(key); + var contains = Data.ContainsKey(key) && Data[key] != null; return contains; } diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index edfb88cbfe6..bc924926c1c 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -61,7 +61,7 @@ public static void Save() { lock (_storage) { - _storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, y => y.usage)); + _storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, x => x.usage)); } } @@ -211,6 +211,15 @@ private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = option); } + public static bool CacheContainImage(string path) + { + return ImageCache.ContainsKey(path); + } + public static ImageSource LoadDefault(bool loadFullImage = false) + { + return LoadInternal(Constant.MissingImgIcon, loadFullImage).ImageSource; + } + public static ImageSource Load(string path, bool loadFullImage = false) { var imageResult = LoadInternal(path, loadFullImage); diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index a64836285e4..76c2a3e48d0 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using System.Windows.Threading; @@ -26,18 +27,18 @@ public ResultViewModel(Result result, Settings settings) public Settings Settings { get; private set; } - public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Hidden; + public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Hidden; public string OpenResultModifiers => Settings.OpenResultModifiers; public string ShowTitleToolTip => string.IsNullOrEmpty(Result.TitleToolTip) - ? Result.Title + ? Result.Title : Result.TitleToolTip; public string ShowSubTitleToolTip => string.IsNullOrEmpty(Result.SubTitleToolTip) - ? Result.SubTitle + ? Result.SubTitle : Result.SubTitleToolTip; - + public Lazy Image { get; set; } private ImageSource SetImage @@ -57,9 +58,20 @@ private ImageSource SetImage imagePath = Constant.MissingImgIcon; } } - - // will get here either when icoPath has value\icon delegate is null\when had exception in delegate - return ImageLoader.Load(imagePath); + + if (ImageLoader.CacheContainImage(imagePath)) + // will get here either when icoPath has value\icon delegate is null\when had exception in delegate + return ImageLoader.Load(imagePath); + else + { + Task.Run(() => + { + Image = new Lazy(() => ImageLoader.Load(imagePath)); + OnPropertyChanged(nameof(Image)); + }); + + return ImageLoader.LoadDefault(); + } } } @@ -78,7 +90,7 @@ public override bool Equals(object obj) } } - + public override int GetHashCode() { return Result.GetHashCode(); diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 1db6ac62499..eb40e75de11 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -201,9 +201,10 @@ private List NewResults(List newRawResults, string resu var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList(); + return results.Where(r => r.Result.PluginID != resultId) - .Concat(newResults) + .Concat(results.Intersect(newResults).Union(newResults)) .OrderByDescending(r => r.Result.Score) .ToList(); } From 4dd4cdafbf24c7437785cf01407aa7f6ce4086c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 15 Nov 2020 21:00:31 +0800 Subject: [PATCH 17/62] Revert "Use Virtualizing StackPanel instead of GridView to greatly reduce the loading resources." This reverts commit c89ee33c5b6c72fab2aee25f6584b83c776dec03. Revert it because it is not useful so just keep the original code. --- Flow.Launcher/ResultListBox.xaml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 350ef018e12..072196605a6 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -16,7 +16,6 @@ Style="{DynamicResource BaseListboxStyle}" Focusable="False" KeyboardNavigation.DirectionalNavigation="Cycle" SelectionMode="Single" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Standard" - ScrollViewer.IsDeferredScrollingEnabled="True" SelectionChanged="OnSelectionChanged" IsSynchronizedWithCurrentItem="True" PreviewMouseDown="ListBox_PreviewMouseDown"> @@ -30,20 +29,26 @@ - - + + - + + + + + + - + - + @@ -55,7 +60,7 @@ - + @@ -76,7 +81,7 @@ - + From b1dd7b418136c791e0331ec7711421f285a2a54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 15 Nov 2020 21:03:39 +0800 Subject: [PATCH 18/62] Move Image Cache filtering earlier --- Flow.Launcher.Infrastructure/Image/ImageCache.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 7482ac1cfec..cce0aaace8c 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -26,7 +26,7 @@ public class ImageCache private const int MaxCached = 50; public ConcurrentDictionary Data { get; private set; } = new ConcurrentDictionary(); private const int permissibleFactor = 2; - + public void Initialization(Dictionary usage) { foreach (var key in usage.Keys) @@ -65,15 +65,13 @@ public ImageSource this[string path] if (Data.Count > permissibleFactor * MaxCached) { // To delete the images from the data dictionary based on the resizing of the Usage Dictionary. - - - foreach (var key in Data.Where(x => x.Key != Constant.MissingImgIcon) + foreach (var key in Data + .Where(x => x.Key != Constant.MissingImgIcon + && x.Key != Constant.ErrorIcon + && x.Key != Constant.DefaultIcon) .OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) { - if (!(key.Equals(Constant.ErrorIcon) || key.Equals(Constant.DefaultIcon))) - { - Data.TryRemove(key, out _); - } + Data.TryRemove(key, out _); } } } From 2161f27a88180c74ffc13046e8557a94b498a426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 08:32:11 +0800 Subject: [PATCH 19/62] fix unintended token argument pass --- Flow.Launcher/ViewModel/MainViewModel.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index c093495e314..21243a79306 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -22,6 +22,7 @@ using System.Windows.Media; using Flow.Launcher.Infrastructure.Image; using System.Collections.Concurrent; +using Flow.Launcher.Infrastructure.Logger; namespace Flow.Launcher.ViewModel { @@ -442,7 +443,7 @@ private void QueryResults() if (!plugin.Metadata.Disabled) { var results = PluginManager.QueryForPlugin(plugin, query); - _resultsUpdateQueue.Add(new ResultsForUpdate(results, plugin.Metadata, query, _updateToken)); + _resultsUpdateQueue.Add(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)); } }); } @@ -695,6 +696,20 @@ public void Save() /// public void UpdateResultView(IEnumerable resultsForUpdates) { + if (!resultsForUpdates.Any()) + return; + + try + { + var token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); + } + catch (Exception e) + { + Log.Debug("Illegal token information"); + } + + + foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) { if (_topMostRecord.IsTopMost(result)) From e7c02dac7201c347ae9aa03ce2d100b29f3e2ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 10:12:32 +0800 Subject: [PATCH 20/62] change SetImage to method instead of property to avoid unintended use --- Flow.Launcher/ViewModel/ResultViewModel.cs | 47 ++++++++++------------ 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 76c2a3e48d0..8ce35227f08 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -19,7 +19,7 @@ public ResultViewModel(Result result, Settings settings) if (result != null) { Result = result; - Image = new Lazy(() => SetImage); + Image = new Lazy(SetImage); } Settings = settings; @@ -41,37 +41,34 @@ public ResultViewModel(Result result, Settings settings) public Lazy Image { get; set; } - private ImageSource SetImage + private ImageSource SetImage() { - get + var imagePath = Result.IcoPath; + if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) { - var imagePath = Result.IcoPath; - if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) + try { - try - { - return Result.Icon(); - } - catch (Exception e) - { - Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); - imagePath = Constant.MissingImgIcon; - } + return Result.Icon(); } + catch (Exception e) + { + Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); + imagePath = Constant.MissingImgIcon; + } + } - if (ImageLoader.CacheContainImage(imagePath)) - // will get here either when icoPath has value\icon delegate is null\when had exception in delegate - return ImageLoader.Load(imagePath); - else + if (ImageLoader.CacheContainImage(imagePath)) + // will get here either when icoPath has value\icon delegate is null\when had exception in delegate + return ImageLoader.Load(imagePath); + else + { + Task.Run(() => { - Task.Run(() => - { - Image = new Lazy(() => ImageLoader.Load(imagePath)); - OnPropertyChanged(nameof(Image)); - }); + Image = new Lazy(() => ImageLoader.Load(imagePath)); + OnPropertyChanged(nameof(Image)); + }); - return ImageLoader.LoadDefault(); - } + return ImageLoader.LoadDefault(); } } From 0a9bad3ffa25a2069f4e736e65b8a65a37ae44b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 12:46:39 +0800 Subject: [PATCH 21/62] Use Customized LazyAsync class to load image instead of the weird way of updating lazy class async --- .../Image/ImageLoader.cs | 12 ++-- Flow.Launcher/ViewModel/ResultViewModel.cs | 56 +++++++++++++++---- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index bc924926c1c..f61d4615a47 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -18,6 +18,8 @@ public static class ImageLoader private static readonly ConcurrentDictionary GuidToKey = new ConcurrentDictionary(); private static IImageHashGenerator _hashGenerator; private static bool EnableImageHash = true; + public static ImageSource defaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); + private static readonly string[] ImageExtensions = { @@ -37,6 +39,7 @@ public static void Initialize() var usage = LoadStorageToConcurrentDictionary(); + foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) { ImageSource img = new BitmapImage(new Uri(icon)); @@ -213,13 +216,10 @@ private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = public static bool CacheContainImage(string path) { - return ImageCache.ContainsKey(path); - } - public static ImageSource LoadDefault(bool loadFullImage = false) - { - return LoadInternal(Constant.MissingImgIcon, loadFullImage).ImageSource; + return ImageCache.ContainsKey(path) && ImageCache[path] != null; } + public static ImageSource Load(string path, bool loadFullImage = false) { var imageResult = LoadInternal(path, loadFullImage); @@ -230,7 +230,7 @@ public static ImageSource Load(string path, bool loadFullImage = false) string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; if (hash != null) { - + if (GuidToKey.TryGetValue(hash, out string key)) { // image already exists img = ImageCache[key] ?? img; diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 8ce35227f08..511df5e59e5 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -8,18 +8,57 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; - +using Microsoft.FSharp.Core; namespace Flow.Launcher.ViewModel { public class ResultViewModel : BaseModel { + public class LazyAsync : Lazy> + { + private T defaultValue; + + + private readonly Action _updateCallback; + public T Value + { + get + { + if (!IsValueCreated) + { + base.Value.ContinueWith(_ => + { + _updateCallback(); + }); + return defaultValue; + } + else if (!base.Value.IsCompleted) + { + return defaultValue; + } + else return base.Value.Result; + } + } + public LazyAsync(Func> factory, T defaultValue, Action updateCallback) : base(factory) + { + if (defaultValue != null) + { + this.defaultValue = defaultValue; + } + _updateCallback = updateCallback; + + } + } + public ResultViewModel(Result result, Settings settings) { if (result != null) { Result = result; - Image = new Lazy(SetImage); + Image = new LazyAsync(SetImage, ImageLoader.defaultImage, () => + { + OnPropertyChanged(nameof(Image)); + }); } Settings = settings; @@ -39,9 +78,9 @@ public ResultViewModel(Result result, Settings settings) ? Result.SubTitle : Result.SubTitleToolTip; - public Lazy Image { get; set; } + public LazyAsync Image { get; set; } - private ImageSource SetImage() + private async Task SetImage() { var imagePath = Result.IcoPath; if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) @@ -62,14 +101,9 @@ private ImageSource SetImage() return ImageLoader.Load(imagePath); else { - Task.Run(() => - { - Image = new Lazy(() => ImageLoader.Load(imagePath)); - OnPropertyChanged(nameof(Image)); - }); - - return ImageLoader.LoadDefault(); + return await Task.Run(() => ImageLoader.Load(imagePath)); } + } public Result Result { get; } From d9ef68272b1b98d727d00096203ddd3fea1abfbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 12:58:53 +0800 Subject: [PATCH 22/62] change default image property name --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 2 +- Flow.Launcher/ViewModel/ResultViewModel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index f61d4615a47..fb2f426a0fe 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -18,7 +18,7 @@ public static class ImageLoader private static readonly ConcurrentDictionary GuidToKey = new ConcurrentDictionary(); private static IImageHashGenerator _hashGenerator; private static bool EnableImageHash = true; - public static ImageSource defaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); + public static ImageSource DefaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); private static readonly string[] ImageExtensions = diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 511df5e59e5..f4a51070fd7 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -55,7 +55,7 @@ public ResultViewModel(Result result, Settings settings) if (result != null) { Result = result; - Image = new LazyAsync(SetImage, ImageLoader.defaultImage, () => + Image = new LazyAsync(SetImage, ImageLoader.DefaultImage, () => { OnPropertyChanged(nameof(Image)); }); From 71cac9cb2335bdd409a08bd8a2c0b30459889a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 13:04:16 +0800 Subject: [PATCH 23/62] change the way of checking whether cache exist --- Flow.Launcher.Infrastructure/Image/ImageCache.cs | 3 +-- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index cce0aaace8c..c3d7e5cfae3 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -79,8 +79,7 @@ public ImageSource this[string path] public bool ContainsKey(string key) { - var contains = Data.ContainsKey(key) && Data[key] != null; - return contains; + return Data.ContainsKey(key) && Data[key].imageSource != null; } public int CacheSize() diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index fb2f426a0fe..92cf9ac3fb6 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -216,7 +216,7 @@ private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = public static bool CacheContainImage(string path) { - return ImageCache.ContainsKey(path) && ImageCache[path] != null; + return ImageCache.ContainsKey(path); } From 23d9e253baed19786806f9236c5b74db7cdc56ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 19:20:52 +0800 Subject: [PATCH 24/62] Add some cancellationtoken check --- Flow.Launcher/ViewModel/MainViewModel.cs | 15 +++------------ Flow.Launcher/ViewModel/ResultsViewModel.cs | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 21243a79306..ccc351cd399 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -698,16 +698,7 @@ public void UpdateResultView(IEnumerable resultsForUpdates) { if (!resultsForUpdates.Any()) return; - - try - { - var token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); - } - catch (Exception e) - { - Log.Debug("Illegal token information"); - } - + CancellationToken token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) @@ -722,10 +713,10 @@ public void UpdateResultView(IEnumerable resultsForUpdates) } } - Results.AddResults(resultsForUpdates); + Results.AddResults(resultsForUpdates, token); } - /// + /// U /// To avoid deadlock, this method should not called from main thread /// public void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index eb40e75de11..cce53296521 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; +using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -136,7 +137,6 @@ public void KeepResultsExcept(PluginMetadata metadata) public void AddResults(List newRawResults, string resultId) { var newResults = NewResults(newRawResults, resultId); - lock (_collectionLock) { // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf @@ -163,9 +163,11 @@ public void AddResults(List newRawResults, string resultId) /// /// To avoid deadlock, this method should not called from main thread /// - public void AddResults(IEnumerable resultsForUpdates) + public void AddResults(IEnumerable resultsForUpdates, CancellationToken token) { var newResults = NewResults(resultsForUpdates); + if (token.IsCancellationRequested) + return; lock (_collectionLock) { @@ -173,7 +175,7 @@ public void AddResults(IEnumerable resultsForUpdates) // fix selected index flow SelectedIndex = 0; - Results.Update(newResults); + Results.Update(newResults, token); } switch (Visbility) @@ -201,7 +203,7 @@ private List NewResults(List newRawResults, string resu var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList(); - + return results.Where(r => r.Result.PluginID != resultId) .Concat(results.Intersect(newResults).Union(newResults)) @@ -292,10 +294,17 @@ public void RemoveAll() /// Update the results collection with new results, try to keep identical results /// /// - public void Update(List newItems) + public void Update(List newItems, CancellationToken token) { Clear(); + if (token.IsCancellationRequested) + return; + AddRange(newItems); + } + public void Update(List newItems) + { + Clear(); AddRange(newItems); } } From 2b423d9d80b3c6ced13c78feeec7d867eb3e3b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 17 Nov 2020 16:46:36 +0800 Subject: [PATCH 25/62] Use single add notify for each element when the new Item is small or the collection hasn't been buffer a lot. This seems solve the startup stunt and potentially make render faster. (Need Testing) --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 47 ++++++++++++++++----- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index cce53296521..3a4b07fd669 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.ComponentModel; +using System.Configuration; using System.Linq; using System.Threading; using System.Windows; @@ -137,6 +139,7 @@ public void KeepResultsExcept(PluginMetadata metadata) public void AddResults(List newRawResults, string resultId) { var newResults = NewResults(newRawResults, resultId); + lock (_collectionLock) { // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf @@ -258,30 +261,38 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP } #endregion - public class ResultCollection : ObservableCollection, INotifyCollectionChanged + public class ResultCollection : ObservableCollection { private bool _suppressNotification = false; + + private long editTime = 0; + + // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (!_suppressNotification) base.OnCollectionChanged(e); } - public override event NotifyCollectionChangedEventHandler CollectionChanged; - - // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ - public void AddRange(IEnumerable Items) + public void BulkAddRange(IEnumerable resultViews) { _suppressNotification = true; + foreach (var item in resultViews) + { + Add(item); + } + _suppressNotification = false; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + public void AddRange(IEnumerable Items) + { foreach (var item in Items) { Add(item); } - // wpf use directx / double buffered already, so just reset all won't cause ui flickering - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); return; } @@ -296,16 +307,32 @@ public void RemoveAll() /// public void Update(List newItems, CancellationToken token) { - Clear(); + if (token.IsCancellationRequested) return; - AddRange(newItems); + Update(newItems); } public void Update(List newItems) { + if (editTime == 0) + { + AddRange(newItems); + editTime++; + return; + } + else if (editTime < 15 || newItems.Count < 50) + { + ClearItems(); + AddRange(newItems); + editTime++; + return; + } Clear(); - AddRange(newItems); + + BulkAddRange(newItems); + editTime++; + } } } From 6863b412e3060f3ff1d69a08c40891fb5d5182ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 20 Nov 2020 22:45:33 +0800 Subject: [PATCH 26/62] Use TPL's BufferBlock instead of BlockingCollection, which will block the current Thread. This should make the Task.Run works better. --- Flow.Launcher/ViewModel/MainViewModel.cs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ccc351cd399..b6bb6703a32 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -23,6 +23,7 @@ using Flow.Launcher.Infrastructure.Image; using System.Collections.Concurrent; using Flow.Launcher.Infrastructure.Logger; +using System.Threading.Tasks.Dataflow; namespace Flow.Launcher.ViewModel { @@ -49,7 +50,7 @@ public class MainViewModel : BaseModel, ISavable private bool _saved; private readonly Internationalization _translator = InternationalizationManager.Instance; - private BlockingCollection _resultsUpdateQueue; + private BufferBlock _resultsUpdateQueue; #endregion @@ -94,10 +95,10 @@ private void RegisterResultsUpdatedEvent() var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => { - Task.Run(() => + Task.Run(async () => { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - _resultsUpdateQueue.Add(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); + await _resultsUpdateQueue.SendAsync(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); }, _updateToken); }; } @@ -105,20 +106,14 @@ private void RegisterResultsUpdatedEvent() private void RegisterResultUpdate() { - _resultsUpdateQueue = new BlockingCollection(); + _resultsUpdateQueue = new BufferBlock(); Task.Run(async () => { - while (true) + while (await _resultsUpdateQueue.OutputAvailableAsync()) { - List queue = new List() { _resultsUpdateQueue.Take() }; await Task.Delay(30); - - while (_resultsUpdateQueue.TryTake(out var resultsForUpdate)) - { - queue.Add(resultsForUpdate); - } - + _resultsUpdateQueue.TryReceiveAll(out var queue); UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); } @@ -443,7 +438,7 @@ private void QueryResults() if (!plugin.Metadata.Disabled) { var results = PluginManager.QueryForPlugin(plugin, query); - _resultsUpdateQueue.Add(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)); + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)); } }); } From ea5e9a0fae49a9e9bdeee4791509764ffe07452e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 20 Nov 2020 23:05:12 +0800 Subject: [PATCH 27/62] Use New keyword for customized lazyAsync Value property --- Flow.Launcher/ViewModel/ResultViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index f4a51070fd7..0909b342f83 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -20,7 +20,7 @@ public class LazyAsync : Lazy> private readonly Action _updateCallback; - public T Value + public new T Value { get { From 4fb884cf570da817d6286883050308b31313f505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 20 Nov 2020 23:21:04 +0800 Subject: [PATCH 28/62] Wait 50 ms for query staying same when updating view. --- Flow.Launcher/ViewModel/MainViewModel.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index b6bb6703a32..6f67ffcf5a6 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -229,6 +229,7 @@ private void InitializeKeyCommands() public ResultsViewModel Results { get; private set; } public ResultsViewModel ContextMenu { get; private set; } public ResultsViewModel History { get; private set; } + private string _lastQueryText; private string _queryText; public string QueryText @@ -427,8 +428,14 @@ private void QueryResults() }, currentCancellationToken); var plugins = PluginManager.ValidPluginsForQuery(query); - Task.Run(() => + Task.Run(async () => { + // Wait 50 millisecond for query change + // if query stay the same, update the view + await Task.Delay(50); + if (!(_lastQuery.RawQuery == QueryText)) + return; + // so looping will stop once it was cancelled var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; try From f11b302c37381c65ed50aa95ac07dc54b383ce7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 21 Nov 2020 12:01:57 +0800 Subject: [PATCH 29/62] slighly reduce the delay time --- Flow.Launcher/ViewModel/MainViewModel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 6f67ffcf5a6..fe1062cbb42 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -112,7 +112,7 @@ private void RegisterResultUpdate() { while (await _resultsUpdateQueue.OutputAvailableAsync()) { - await Task.Delay(30); + await Task.Delay(20); _resultsUpdateQueue.TryReceiveAll(out var queue); UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); @@ -430,9 +430,9 @@ private void QueryResults() var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => { - // Wait 50 millisecond for query change + // Wait 45 millisecond for query change // if query stay the same, update the view - await Task.Delay(50); + await Task.Delay(45); if (!(_lastQuery.RawQuery == QueryText)) return; From 34dc0d0220e73c9ebe59a6efc1cf232828f68bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 21 Nov 2020 16:42:15 +0800 Subject: [PATCH 30/62] move progressbar task position and change the way of comparison --- Flow.Launcher/ViewModel/MainViewModel.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index fe1062cbb42..4e0285d9e81 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -419,13 +419,6 @@ private void QueryResults() RemoveOldQueryResults(query); _lastQuery = query; - Task.Delay(200, currentCancellationToken).ContinueWith(_ => - { // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet - if (currentUpdateSource == _updateSource && _isQueryRunning) - { - ProgressBarVisibility = Visibility.Visible; - } - }, currentCancellationToken); var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => @@ -433,9 +426,18 @@ private void QueryResults() // Wait 45 millisecond for query change // if query stay the same, update the view await Task.Delay(45); - if (!(_lastQuery.RawQuery == QueryText)) + if (!(_lastQuery.Search == query.Search)) return; + _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => + { + // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet + if (currentUpdateSource == _updateSource && _isQueryRunning) + { + ProgressBarVisibility = Visibility.Visible; + } + }, currentCancellationToken); + // so looping will stop once it was cancelled var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; try From 9bc58fe9dacfd5b78e2c75be53e3ad2bd72dfe21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 22 Nov 2020 18:24:27 +0800 Subject: [PATCH 31/62] Only stop querying for global query --- Flow.Launcher/ViewModel/MainViewModel.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 4e0285d9e81..1f8e72b8c73 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -79,9 +79,10 @@ public MainViewModel(Settings settings) _selectedResults = Results; InitializeKeyCommands(); - RegisterResultsUpdatedEvent(); RegisterResultUpdate(); + RegisterResultsUpdatedEvent(); + SetHotkey(_settings.Hotkey, OnHotkey); SetCustomPluginHotkey(); @@ -423,11 +424,14 @@ private void QueryResults() var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => { - // Wait 45 millisecond for query change - // if query stay the same, update the view - await Task.Delay(45); - if (!(_lastQuery.Search == query.Search)) - return; + if (plugins.Count > 1) + { + // Wait 45 millisecond for query change in global query + // if query changes, return so that it won't be calculated + await Task.Delay(45); + if (!(_lastQuery.Search == query.Search)) + return; + } _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => { From 81a02c6e64ed67825b547d64393b83911cefdf08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 24 Nov 2020 17:38:24 +0800 Subject: [PATCH 32/62] Don't add result to queue unless the query match (plugin event driven update) Remove the Task.Run because updating result metadata and adding result to queue should not be time wasted. --- Flow.Launcher/ViewModel/MainViewModel.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 3d3792c545c..f398df92b3e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -96,11 +96,9 @@ private void RegisterResultsUpdatedEvent() var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => { - Task.Run(async () => - { - PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - await _resultsUpdateQueue.SendAsync(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); - }, _updateToken); + PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); + if (e.Query.Search == _lastQuery.Search) + _resultsUpdateQueue.Post(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); }; } } @@ -428,13 +426,13 @@ private void QueryResults() { // Wait 45 millisecond for query change in global query // if query changes, return so that it won't be calculated - await Task.Delay(45); + await Task.Delay(45, currentCancellationToken); if (!(_lastQuery.Search == query.Search)) return; } _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => - { + { // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet if (currentUpdateSource == _updateSource && _isQueryRunning) { From c1107494344977f51865dbfba96ec5dd13b4ee5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 24 Nov 2020 17:41:57 +0800 Subject: [PATCH 33/62] Add more cancellation --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f398df92b3e..fdae8ecd4db 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -449,7 +449,8 @@ private void QueryResults() if (!plugin.Metadata.Disabled) { var results = PluginManager.QueryForPlugin(plugin, query); - _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)); + if (!currentCancellationToken.IsCancellationRequested) + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)); } }); } From 69e11e2861622d85b957e983e6c736ff745eb5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 24 Nov 2020 18:59:16 +0800 Subject: [PATCH 34/62] Use CancellationToken.IsCancellationRequested wherever is possible to use, check equality of query whererever token is unaccessiable. Dispose CancellationTokenSourse manually --- Flow.Launcher/ViewModel/MainViewModel.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index fdae8ecd4db..9316cd62ec5 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -404,6 +404,8 @@ private void QueryResults() if (!string.IsNullOrEmpty(QueryText)) { _updateSource?.Cancel(); + _updateSource?.Dispose(); + var currentUpdateSource = new CancellationTokenSource(); _updateSource = currentUpdateSource; var currentCancellationToken = _updateSource.Token; @@ -427,14 +429,14 @@ private void QueryResults() // Wait 45 millisecond for query change in global query // if query changes, return so that it won't be calculated await Task.Delay(45, currentCancellationToken); - if (!(_lastQuery.Search == query.Search)) + if (currentCancellationToken.IsCancellationRequested) return; } _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => { // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet - if (currentUpdateSource == _updateSource && _isQueryRunning) + if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) { ProgressBarVisibility = Visibility.Visible; } @@ -463,7 +465,7 @@ private void QueryResults() // this should happen once after all queries are done so progress bar should continue // until the end of all querying _isQueryRunning = false; - if (currentUpdateSource == _updateSource) + if (!currentCancellationToken.IsCancellationRequested) { // update to hidden if this is still the current query ProgressBarVisibility = Visibility.Hidden; } From 1c6769cbcc78b2988ded5f7b22e60a1590d1b382 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 27 Nov 2020 07:43:03 +1100 Subject: [PATCH 35/62] fix merge --- Flow.Launcher/ViewModel/ResultViewModel.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 6805f31aa08..3a1b7fee4fc 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -100,17 +100,10 @@ private async Task SetImage() } if (ImageLoader.CacheContainImage(imagePath)) - { // will get here either when icoPath has value\icon delegate is null\when had exception in delegate return ImageLoader.Load(imagePath); - else - { - return await Task.Run(() => ImageLoader.Load(imagePath)); - } - else - { - return await Task.Run(() => ImageLoader.Load(imagePath)); - } + + return await Task.Run(() => ImageLoader.Load(imagePath)); } public Result Result { get; } From 05aa7062d7104b6d2bbb900ad26607f5f5db8a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 27 Nov 2020 08:46:59 +0800 Subject: [PATCH 36/62] Change ErrorIcon to MissingImgIcon in ImageCache check since we don't use ErrorIcon anymore --- Flow.Launcher.Infrastructure/Image/ImageCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index fdbdddb2d0e..b8da3b030c6 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -66,7 +66,7 @@ public ImageSource this[string path] { // To delete the images from the data dictionary based on the resizing of the Usage Dictionary. foreach (var key in Data - .Where(x => x.Key != Constant.ErrorIcon + .Where(x => x.Key != Constant.MissingImgIcon && x.Key != Constant.DefaultIcon) .OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) { From 813e9b5439592fb3057bf5ff8af76d50d2df8f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 28 Nov 2020 13:25:49 +0800 Subject: [PATCH 37/62] fix selected item doesn't always be the first item issue. Method: Not only update selected index, but also update selected item when adding results. --- Flow.Launcher/ResultListBox.xaml | 2 +- Flow.Launcher/ViewModel/ResultsViewModel.cs | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 072196605a6..2f9d06d814e 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -9,7 +9,7 @@ d:DataContext="{d:DesignInstance vm:ResultsViewModel}" MaxHeight="{Binding MaxHeight}" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" - SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}" + SelectedItem="{Binding SelectedItem, Mode=TwoWay}" HorizontalContentAlignment="Stretch" ItemsSource="{Binding Results}" Margin="{Binding Margin}" Visibility="{Binding Visbility}" diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 3a4b07fd669..38464739513 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -144,11 +144,10 @@ public void AddResults(List newRawResults, string resultId) { // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow - if (Results.Count > 0) - SelectedIndex = 0; // update UI in one run, so it can avoid UI flickering Results.Update(newResults); + SelectedItem = newResults[0]; } if (Visbility != Visibility.Visible && Results.Count > 0) @@ -176,9 +175,11 @@ public void AddResults(IEnumerable resultsForUpdates, Cancella { // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow - SelectedIndex = 0; Results.Update(newResults, token); + SelectedItem = newResults[0]; + + } switch (Visbility) @@ -321,13 +322,6 @@ public void Update(List newItems) editTime++; return; } - else if (editTime < 15 || newItems.Count < 50) - { - ClearItems(); - AddRange(newItems); - editTime++; - return; - } Clear(); BulkAddRange(newItems); From a640eadf1b99fd53787a32c5b4921cac38870914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 29 Nov 2020 14:56:08 +0800 Subject: [PATCH 38/62] Use List instead of ObservableCollection as base class for resultcollection for better customized control of event --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 62 +++++++++++---------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 38464739513..6d476ec63d2 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -12,6 +12,7 @@ using System.Windows.Documents; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Microsoft.FSharp.Control; namespace Flow.Launcher.ViewModel { @@ -177,6 +178,8 @@ public void AddResults(IEnumerable resultsForUpdates, Cancella // fix selected index flow Results.Update(newResults, token); + if (token.IsCancellationRequested) + return; SelectedItem = newResults[0]; @@ -262,35 +265,33 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP } #endregion - public class ResultCollection : ObservableCollection + public class ResultCollection : List, INotifyCollectionChanged { - private bool _suppressNotification = false; + + public event NotifyCollectionChangedEventHandler CollectionChanged; private long editTime = 0; + // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ - protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - if (!_suppressNotification) - base.OnCollectionChanged(e); + if (CollectionChanged != null && CollectionChanged.GetInvocationList().Length == 1) + CollectionChanged.Invoke(this, e); } - public void BulkAddRange(IEnumerable resultViews) + public void BulkAddRange(IEnumerable resultViews, CancellationToken? token) { - _suppressNotification = true; - foreach (var item in resultViews) - { - Add(item); - } - _suppressNotification = false; + AddRange(resultViews); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - public void AddRange(IEnumerable Items) + public void AddAll(IEnumerable Items, CancellationToken? token) { foreach (var item in Items) { + if (token?.IsCancellationRequested ?? false) return; Add(item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } // wpf use directx / double buffered already, so just reset all won't cause ui flickering @@ -299,33 +300,34 @@ public void AddRange(IEnumerable Items) } public void RemoveAll() { - ClearItems(); + Clear(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// /// Update the results collection with new results, try to keep identical results /// - /// - public void Update(List newItems, CancellationToken token) + /// New Items to add into the list view + /// Cancellation Token + public void Update(List newItems, CancellationToken? token = null) { - - if (token.IsCancellationRequested) + if (newItems.Count == 0 || (token?.IsCancellationRequested ?? false)) return; - Update(newItems); - } + - public void Update(List newItems) - { - if (editTime == 0) + if (editTime < 5 || newItems.Count < 30) { - AddRange(newItems); + if (Count > 0) RemoveAll(); + if (token?.IsCancellationRequested ?? false) return; + AddAll(newItems, token); + editTime++; + } + else + { + Clear(); + BulkAddRange(newItems, token); editTime++; - return; } - Clear(); - - BulkAddRange(newItems); - editTime++; } } From 19c7446552f6242cc43981948ea5eb24783f5dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 2 Dec 2020 13:56:50 +0800 Subject: [PATCH 39/62] Add Lock to the place we directly update the ResultCollection --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 6d476ec63d2..d373b0fda20 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -120,17 +120,20 @@ public void SelectFirstResult() public void Clear() { - Results.RemoveAll(); + lock (_collectionLock) + Results.RemoveAll(); } public void KeepResultsFor(PluginMetadata metadata) { - Results.Update(Results.Where(r => r.Result.PluginID == metadata.ID).ToList()); + lock (_collectionLock) + Results.Update(Results.Where(r => r.Result.PluginID == metadata.ID).ToList()); } public void KeepResultsExcept(PluginMetadata metadata) { - Results.Update(Results.Where(r => r.Result.PluginID != metadata.ID).ToList()); + lock (_collectionLock) + Results.Update(Results.Where(r => r.Result.PluginID != metadata.ID).ToList()); } @@ -148,7 +151,8 @@ public void AddResults(List newRawResults, string resultId) // update UI in one run, so it can avoid UI flickering Results.Update(newResults); - SelectedItem = newResults[0]; + if (newResults.Any()) + SelectedItem = newResults[0]; } if (Visbility != Visibility.Visible && Results.Count > 0) @@ -304,6 +308,9 @@ public void RemoveAll() OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } + + + /// /// Update the results collection with new results, try to keep identical results /// @@ -311,9 +318,9 @@ public void RemoveAll() /// Cancellation Token public void Update(List newItems, CancellationToken? token = null) { - if (newItems.Count == 0 || (token?.IsCancellationRequested ?? false)) + if (token?.IsCancellationRequested ?? false) return; - + if (editTime < 5 || newItems.Count < 30) { From 98e6a1ab8a203201d08de9a8f1bc5273c81a7789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 2 Dec 2020 20:57:23 +0800 Subject: [PATCH 40/62] Revert using List instead of observablecollection --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 62 ++++++++++----------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d373b0fda20..5a9043c23d3 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -12,7 +12,6 @@ using System.Windows.Documents; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using Microsoft.FSharp.Control; namespace Flow.Launcher.ViewModel { @@ -182,8 +181,6 @@ public void AddResults(IEnumerable resultsForUpdates, Cancella // fix selected index flow Results.Update(newResults, token); - if (token.IsCancellationRequested) - return; SelectedItem = newResults[0]; @@ -269,33 +266,35 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP } #endregion - public class ResultCollection : List, INotifyCollectionChanged + public class ResultCollection : ObservableCollection { - - public event NotifyCollectionChangedEventHandler CollectionChanged; + private bool _suppressNotification = false; private long editTime = 0; - // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ - protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - if (CollectionChanged != null && CollectionChanged.GetInvocationList().Length == 1) - CollectionChanged.Invoke(this, e); + if (!_suppressNotification) + base.OnCollectionChanged(e); } - public void BulkAddRange(IEnumerable resultViews, CancellationToken? token) + public void BulkAddRange(IEnumerable resultViews) { - AddRange(resultViews); + _suppressNotification = true; + foreach (var item in resultViews) + { + Add(item); + } + _suppressNotification = false; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } - public void AddAll(IEnumerable Items, CancellationToken? token) + public void AddRange(IEnumerable Items) { foreach (var item in Items) { - if (token?.IsCancellationRequested ?? false) return; Add(item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } // wpf use directx / double buffered already, so just reset all won't cause ui flickering @@ -304,8 +303,7 @@ public void AddAll(IEnumerable Items, CancellationToken? token) } public void RemoveAll() { - Clear(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ClearItems(); } @@ -314,27 +312,27 @@ public void RemoveAll() /// /// Update the results collection with new results, try to keep identical results /// - /// New Items to add into the list view - /// Cancellation Token - public void Update(List newItems, CancellationToken? token = null) + /// + public void Update(List newItems, CancellationToken token) { - if (token?.IsCancellationRequested ?? false) - return; + if (token.IsCancellationRequested) + return; + Update(newItems); + } - if (editTime < 5 || newItems.Count < 30) - { - if (Count > 0) RemoveAll(); - if (token?.IsCancellationRequested ?? false) return; - AddAll(newItems, token); - editTime++; - } - else + public void Update(List newItems) + { + if (editTime == 0) { - Clear(); - BulkAddRange(newItems, token); + AddRange(newItems); editTime++; + return; } + Clear(); + + BulkAddRange(newItems); + editTime++; } } From e7dd8676ec247f3a670867f4cb75a4263228cb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 2 Dec 2020 23:37:23 +0800 Subject: [PATCH 41/62] Change some code and check out what cause those error --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 65 ++++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 5a9043c23d3..e05e3c15329 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -141,17 +141,18 @@ public void KeepResultsExcept(PluginMetadata metadata) /// public void AddResults(List newRawResults, string resultId) { - var newResults = NewResults(newRawResults, resultId); lock (_collectionLock) { + var newResults = NewResults(newRawResults, resultId); + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow // update UI in one run, so it can avoid UI flickering Results.Update(newResults); - if (newResults.Any()) - SelectedItem = newResults[0]; + if (Results.Any()) + SelectedItem = Results[0]; } if (Visbility != Visibility.Visible && Results.Count > 0) @@ -171,17 +172,20 @@ public void AddResults(List newRawResults, string resultId) /// public void AddResults(IEnumerable resultsForUpdates, CancellationToken token) { - var newResults = NewResults(resultsForUpdates); - if (token.IsCancellationRequested) - return; - lock (_collectionLock) { + var newResults = NewResults(resultsForUpdates); + if (token.IsCancellationRequested) + return; + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow Results.Update(newResults, token); - SelectedItem = newResults[0]; + if (token.IsCancellationRequested) + return; + if (Results.Any()) + SelectedItem = Results[0]; } @@ -268,33 +272,38 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP public class ResultCollection : ObservableCollection { - private bool _suppressNotification = false; private long editTime = 0; - // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ + private bool _suppressNotifying = false; + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - if (!_suppressNotification) - base.OnCollectionChanged(e); + if (_suppressNotifying) + return; + base.OnCollectionChanged(e); } public void BulkAddRange(IEnumerable resultViews) { - _suppressNotification = true; + _suppressNotifying = true; + foreach (var item in resultViews) { Add(item); } - _suppressNotification = false; + _suppressNotifying = false; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - public void AddRange(IEnumerable Items) + public void AddRange(IEnumerable Items, CancellationToken? token) { foreach (var item in Items) { + if (token?.IsCancellationRequested ?? false) + return; + Add(item); + } // wpf use directx / double buffered already, so just reset all won't cause ui flickering @@ -313,27 +322,25 @@ public void RemoveAll() /// Update the results collection with new results, try to keep identical results /// /// - public void Update(List newItems, CancellationToken token) + public void Update(List newItems, CancellationToken? token = null) { - if (token.IsCancellationRequested) + if (token?.IsCancellationRequested ?? false) return; - Update(newItems); - } - public void Update(List newItems) - { - if (editTime == 0) + if (editTime < 5 || newItems.Count < 30) { - AddRange(newItems); + if (Count != 0) ClearItems(); + AddRange(newItems, token); editTime++; return; } - Clear(); - - BulkAddRange(newItems); - editTime++; - + else + { + Clear(); + BulkAddRange(newItems); + editTime++; + } } } } From 25bc2a70e3091de08f1a4a74050151c008c97bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 3 Dec 2020 10:33:19 +0800 Subject: [PATCH 42/62] Add timeout for waiting collectionchange event, although seems the deadlock isn't caused by this. --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 51 ++++++++++++--------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index e05e3c15329..40021690ba2 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -6,10 +6,12 @@ using System.Configuration; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; +using System.Windows.Forms; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -172,12 +174,11 @@ public void AddResults(List newRawResults, string resultId) /// public void AddResults(IEnumerable resultsForUpdates, CancellationToken token) { + var newResults = NewResults(resultsForUpdates); + if (token.IsCancellationRequested) + return; lock (_collectionLock) { - var newResults = NewResults(resultsForUpdates); - if (token.IsCancellationRequested) - return; - // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow @@ -186,8 +187,6 @@ public void AddResults(IEnumerable resultsForUpdates, Cancella return; if (Results.Any()) SelectedItem = Results[0]; - - } switch (Visbility) @@ -277,38 +276,46 @@ public class ResultCollection : ObservableCollection private bool _suppressNotifying = false; + private CancellationToken _token; + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - if (_suppressNotifying) - return; - base.OnCollectionChanged(e); + if (!_suppressNotifying) + { + var notifyChangeTask = Task.Run(() => base.OnCollectionChanged(e)); + if (notifyChangeTask.Wait(300)) + return; + else + { + notifyChangeTask.Dispose(); + throw new TimeoutException(); + } + } } public void BulkAddRange(IEnumerable resultViews) { + // suppress notifying before adding all element _suppressNotifying = true; - foreach (var item in resultViews) { Add(item); } _suppressNotifying = false; + // manually update event + // wpf use directx / double buffered already, so just reset all won't cause ui flickering + if (_token.IsCancellationRequested) + return; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - public void AddRange(IEnumerable Items, CancellationToken? token) + public void AddRange(IEnumerable Items) { foreach (var item in Items) { - if (token?.IsCancellationRequested ?? false) + if (_token.IsCancellationRequested) return; - Add(item); - } - - // wpf use directx / double buffered already, so just reset all won't cause ui flickering - return; - } public void RemoveAll() { @@ -322,16 +329,16 @@ public void RemoveAll() /// Update the results collection with new results, try to keep identical results /// /// - public void Update(List newItems, CancellationToken? token = null) + public void Update(List newItems, CancellationToken token = default) { - - if (token?.IsCancellationRequested ?? false) + _token = token; + if (_token.IsCancellationRequested) return; if (editTime < 5 || newItems.Count < 30) { if (Count != 0) ClearItems(); - AddRange(newItems, token); + AddRange(newItems); editTime++; return; } From eb13a18d52d1e0659e8896d3bfc4dc3961fad445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 3 Dec 2020 15:38:00 +0800 Subject: [PATCH 43/62] Use task.run in addresult instead of CollectionChange event. --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 45 +++++++++++++-------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 40021690ba2..924fd7ffc82 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -150,11 +150,20 @@ public void AddResults(List newRawResults, string resultId) // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow + var updateTask = Task.Run(() => + { + // update UI in one run, so it can avoid UI flickering + + Results.Update(newResults); + if (Results.Any()) + SelectedItem = Results[0]; + }); + if (!updateTask.Wait(300)) + { + updateTask.Dispose(); + throw new TimeoutException("Update result use too much time."); + } - // update UI in one run, so it can avoid UI flickering - Results.Update(newResults); - if (Results.Any()) - SelectedItem = Results[0]; } if (Visbility != Visibility.Visible && Results.Count > 0) @@ -179,14 +188,23 @@ public void AddResults(IEnumerable resultsForUpdates, Cancella return; lock (_collectionLock) { + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow + var updateTask = Task.Run(() => + { + // update UI in one run, so it can avoid UI flickering + + Results.Update(newResults, token); + if (Results.Any()) + SelectedItem = Results[0]; + }); + if (!updateTask.Wait(300)) + { + updateTask.Dispose(); + throw new TimeoutException("Update result use too much time."); + } - Results.Update(newResults, token); - if (token.IsCancellationRequested) - return; - if (Results.Any()) - SelectedItem = Results[0]; } switch (Visbility) @@ -282,14 +300,7 @@ protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (!_suppressNotifying) { - var notifyChangeTask = Task.Run(() => base.OnCollectionChanged(e)); - if (notifyChangeTask.Wait(300)) - return; - else - { - notifyChangeTask.Dispose(); - throw new TimeoutException(); - } + base.OnCollectionChanged(e); } } From 01158fdc66bff9b8a64ff132e50807d4e317cc95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 21:53:34 +0800 Subject: [PATCH 44/62] Add error handling for the resultUpdateTask to make sure result update can continue when error throw. --- Flow.Launcher/ViewModel/MainViewModel.cs | 41 ++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 9316cd62ec5..dd790a98bc9 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -24,6 +24,7 @@ using System.Collections.Concurrent; using Flow.Launcher.Infrastructure.Logger; using System.Threading.Tasks.Dataflow; +using NLog; namespace Flow.Launcher.ViewModel { @@ -51,6 +52,7 @@ public class MainViewModel : BaseModel, ISavable private readonly Internationalization _translator = InternationalizationManager.Instance; private BufferBlock _resultsUpdateQueue; + private Task _resultsViewUpdateTask; #endregion @@ -80,7 +82,7 @@ public MainViewModel(Settings settings) InitializeKeyCommands(); - RegisterResultUpdate(); + RegisterViewUpdate(); RegisterResultsUpdatedEvent(); @@ -103,20 +105,31 @@ private void RegisterResultsUpdatedEvent() } } - private void RegisterResultUpdate() + private void RegisterViewUpdate() { _resultsUpdateQueue = new BufferBlock(); + _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); - Task.Run(async () => + + async Task updateAction() { while (await _resultsUpdateQueue.OutputAvailableAsync()) { await Task.Delay(20); _resultsUpdateQueue.TryReceiveAll(out var queue); UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); - } - }); + }; + + void continueAction(Task t) + { +#if DEBUG + throw t.Exception; +#else + Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}"); + _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continuationAction, TaskContinuationOptions.OnlyOnFaulted); +#endif + } } @@ -707,7 +720,23 @@ public void UpdateResultView(IEnumerable resultsForUpdates) { if (!resultsForUpdates.Any()) return; - CancellationToken token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); + CancellationToken token; + + try + { + token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); + } +#if DEBUG + catch + { + throw new ArgumentException("Unacceptable token"); + } +#else + catch + { + + } +#endif foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) From aa20d505fe5b42fe198461fa7f77513ec9e36138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 21:54:22 +0800 Subject: [PATCH 45/62] Revert creating a new task when adding result. --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 924fd7ffc82..f12ea23e97f 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -188,22 +188,12 @@ public void AddResults(IEnumerable resultsForUpdates, Cancella return; lock (_collectionLock) { + // update UI in one run, so it can avoid UI flickering - // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf - // fix selected index flow - var updateTask = Task.Run(() => - { - // update UI in one run, so it can avoid UI flickering + Results.Update(newResults, token); + if (Results.Any()) + SelectedItem = Results[0]; - Results.Update(newResults, token); - if (Results.Any()) - SelectedItem = Results[0]; - }); - if (!updateTask.Wait(300)) - { - updateTask.Dispose(); - throw new TimeoutException("Update result use too much time."); - } } From 6267fa9a50f9454e9bdd85ed3dc4015f95822874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 22:18:51 +0800 Subject: [PATCH 46/62] fix typo. --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index dd790a98bc9..3e1580e386c 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -127,7 +127,7 @@ void continueAction(Task t) throw t.Exception; #else Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}"); - _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continuationAction, TaskContinuationOptions.OnlyOnFaulted); + _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); #endif } } From c3d5d4c0cecddc41a5fcdf7b18c1ccd7de87df84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 22:24:25 +0800 Subject: [PATCH 47/62] Don't update collection when both newItems and collection is empty --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index f12ea23e97f..fc77327ccf2 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -333,7 +333,7 @@ public void RemoveAll() public void Update(List newItems, CancellationToken token = default) { _token = token; - if (_token.IsCancellationRequested) + if (Count == 0 && newItems.Count == 0 || _token.IsCancellationRequested) return; if (editTime < 5 || newItems.Count < 30) From 756c5bce3deb3af5df9e9554e930e20609083e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 22:30:14 +0800 Subject: [PATCH 48/62] fix using unintialized variable in release. --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 3e1580e386c..1329421c35d 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -734,7 +734,7 @@ public void UpdateResultView(IEnumerable resultsForUpdates) #else catch { - + token = default; } #endif From b8d3b42295d557784e5ab990a927e612f7fd4872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 5 Dec 2020 15:48:36 +0800 Subject: [PATCH 49/62] use singleordefault instead of single since sometims the queue may be empty but method doesn't return --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 1329421c35d..25768a25eb2 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -724,7 +724,8 @@ public void UpdateResultView(IEnumerable resultsForUpdates) try { - token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); + // Don't know why sometimes even resultsForUpdates is empty, the method won't return; + token = resultsForUpdates.Select(r => r.Token).Distinct().SingleOrDefault(); } #if DEBUG catch From 968931e4a07246f0f15d3878dc50c535d193fdd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Tue, 22 Dec 2020 13:45:11 +0800 Subject: [PATCH 50/62] return if cancellation requested before changing _isQueryRunning state --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 25768a25eb2..a8a8ec1d797 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -474,6 +474,8 @@ private void QueryResults() // nothing to do here } + if (currentCancellationToken.IsCancellationRequested) + return; // this should happen once after all queries are done so progress bar should continue // until the end of all querying From 6b597f7516205a5057cfd4b80ab29a723c10c2de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 17:42:46 +0800 Subject: [PATCH 51/62] throw exception if debug for Log.exception(className, message, exception) --- Flow.Launcher.Infrastructure/Logger/Log.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index 289ec5d6829..d485f964b3e 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -50,14 +50,18 @@ private static bool FormatValid(string message) return valid; } - + [MethodImpl(MethodImplOptions.Synchronized)] public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "") { +#if DEBUG + throw exception; +#else var classNameWithMethod = CheckClassAndMessageAndReturnFullClassWithMethod(className, message, methodName); ExceptionInternal(classNameWithMethod, message, exception); +#endif } private static string CheckClassAndMessageAndReturnFullClassWithMethod(string className, string message, From 0d24aba74fbde39e894be9246e9ae297841e6440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 17:43:48 +0800 Subject: [PATCH 52/62] Use actionkeyword comparison to determine whether delay querying. --- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 1ca44c8b9a1..3ecf301390b 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -437,7 +437,7 @@ private void QueryResults() var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => { - if (plugins.Count > 1) + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { // Wait 45 millisecond for query change in global query // if query changes, return so that it won't be calculated @@ -468,7 +468,7 @@ private void QueryResults() var results = PluginManager.QueryForPlugin(plugin, query); UpdateResultView(results, plugin.Metadata, query); } - catch(Exception e) + catch (Exception e) { Log.Exception("MainViewModel", $"Exception when querying the plugin {plugin.Metadata.Name}", e, "QueryResults"); } From 97052b1fbf3bb826a2dac4c4dcee92879161d32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 10 Jan 2021 11:13:46 +0800 Subject: [PATCH 53/62] fix merge issue --- Flow.Launcher/ViewModel/MainViewModel.cs | 158 ++++++++++++----------- 1 file changed, 82 insertions(+), 76 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 3751d8c611a..ac63f0cc33d 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -54,7 +54,6 @@ public class MainViewModel : BaseModel, ISavable private BufferBlock _resultsUpdateQueue; private Task _resultsViewUpdateTask; - #endregion #region Constructor @@ -95,7 +94,7 @@ private void RegisterResultsUpdatedEvent() { foreach (var pair in PluginManager.GetPluginsForInterface()) { - var plugin = (IResultUpdated)pair.Plugin; + var plugin = (IResultUpdated) pair.Plugin; plugin.ResultsUpdated += (s, e) => { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); @@ -108,7 +107,8 @@ private void RegisterResultsUpdatedEvent() private void RegisterViewUpdate() { _resultsUpdateQueue = new BufferBlock(); - _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); + _resultsViewUpdateTask = + Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); async Task updateAction() @@ -119,7 +119,9 @@ async Task updateAction() _resultsUpdateQueue.TryReceiveAll(out var queue); UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); } - }; + } + + ; void continueAction(Task t) { @@ -127,7 +129,8 @@ void continueAction(Task t) throw t.Exception; #else Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}"); - _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); + _resultsViewUpdateTask = + Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); #endif } } @@ -147,25 +150,13 @@ private void InitializeKeyCommands() } }); - SelectNextItemCommand = new RelayCommand(_ => - { - SelectedResults.SelectNextResult(); - }); + SelectNextItemCommand = new RelayCommand(_ => { SelectedResults.SelectNextResult(); }); - SelectPrevItemCommand = new RelayCommand(_ => - { - SelectedResults.SelectPrevResult(); - }); + SelectPrevItemCommand = new RelayCommand(_ => { SelectedResults.SelectPrevResult(); }); - SelectNextPageCommand = new RelayCommand(_ => - { - SelectedResults.SelectNextPage(); - }); + SelectNextPageCommand = new RelayCommand(_ => { SelectedResults.SelectNextPage(); }); - SelectPrevPageCommand = new RelayCommand(_ => - { - SelectedResults.SelectPrevPage(); - }); + SelectPrevPageCommand = new RelayCommand(_ => { SelectedResults.SelectPrevPage(); }); SelectFirstResultCommand = new RelayCommand(_ => SelectedResults.SelectFirstResult()); @@ -244,6 +235,7 @@ private void InitializeKeyCommands() private string _lastQueryText; private string _queryText; + public string QueryText { get { return _queryText; } @@ -264,10 +256,12 @@ public void ChangeQueryText(string queryText) QueryTextCursorMovedToEnd = true; QueryText = queryText; } + public bool LastQuerySelected { get; set; } public bool QueryTextCursorMovedToEnd { get; set; } private ResultsViewModel _selectedResults; + private ResultsViewModel SelectedResults { get { return _selectedResults; } @@ -299,6 +293,7 @@ private ResultsViewModel SelectedResults QueryText = string.Empty; } } + _selectedResults.Visbility = Visibility.Visible; } } @@ -359,7 +354,7 @@ private void QueryContextMenu() var filtered = results.Where ( r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() - || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() + || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() ).ToList(); ContextMenu.AddResults(filtered, id); } @@ -386,7 +381,7 @@ private void QueryHistory() Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), IcoPath = "Images\\history.png", - OriginQuery = new Query { RawQuery = h.Query }, + OriginQuery = new Query {RawQuery = h.Query}, Action = _ => { SelectedResults = Results; @@ -436,64 +431,73 @@ private void QueryResults() var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => - { - if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { - // Wait 45 millisecond for query change in global query - // if query changes, return so that it won't be calculated - await Task.Delay(45, currentCancellationToken); - if (currentCancellationToken.IsCancellationRequested) - return; - } - - _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => - { - // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet - if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { - ProgressBarVisibility = Visibility.Visible; + // Wait 45 millisecond for query change in global query + // if query changes, return so that it won't be calculated + await Task.Delay(45, currentCancellationToken); + if (currentCancellationToken.IsCancellationRequested) + return; } - }, currentCancellationToken); - // so looping will stop once it was cancelled - var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; - try - { - Parallel.ForEach(plugins, parallelOptions, plugin => + _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => { - if (!plugin.Metadata.Disabled) + // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet + if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) { - try - { - var results = PluginManager.QueryForPlugin(plugin, query); - UpdateResultView(results, plugin.Metadata, query); - } - catch (Exception e) + ProgressBarVisibility = Visibility.Visible; + } + }, currentCancellationToken); + + // so looping will stop once it was cancelled + var parallelOptions = new ParallelOptions {CancellationToken = currentCancellationToken}; + try + { + Parallel.ForEach(plugins, parallelOptions, plugin => + { + if (!plugin.Metadata.Disabled) { - Log.Exception("MainViewModel", $"Exception when querying the plugin {plugin.Metadata.Name}", e, "QueryResults"); + try + { + var results = PluginManager.QueryForPlugin(plugin, query); + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, + query, + currentCancellationToken)); + } + catch (Exception e) + { + Log.Exception("MainViewModel", + $"Exception when querying the plugin {plugin.Metadata.Name}", e, + "QueryResults"); + } } - } - }); - } - catch (OperationCanceledException) - { - // nothing to do here - } - - if (currentCancellationToken.IsCancellationRequested) - return; - - // this should happen once after all queries are done so progress bar should continue - // until the end of all querying - _isQueryRunning = false; - if (!currentCancellationToken.IsCancellationRequested) - { // update to hidden if this is still the current query - ProgressBarVisibility = Visibility.Hidden; - } - }, currentCancellationToken).ContinueWith(t => - { - Log.Exception("MainViewModel", "Error when querying plugins", t.Exception?.InnerException, "QueryResults"); - }, TaskContinuationOptions.OnlyOnFaulted); + }); + } + catch (OperationCanceledException) + { + // nothing to do here + } + + if (currentCancellationToken.IsCancellationRequested) + return; + + // this should happen once after all queries are done so progress bar should continue + // until the end of all querying + _isQueryRunning = false; + if (!currentCancellationToken.IsCancellationRequested) + { + // update to hidden if this is still the current query + ProgressBarVisibility = Visibility.Hidden; + } + }, currentCancellationToken) + .ContinueWith( + t => + { + Log.Exception("MainViewModel", "Error when querying plugins", + t.Exception?.InnerException, + "QueryResults"); + }, TaskContinuationOptions.OnlyOnFaulted); } } else @@ -561,6 +565,7 @@ private Result ContextMenuTopMost(Result result) } }; } + return menu; } @@ -606,6 +611,7 @@ private bool HistorySelected() var selected = SelectedResults == History; return selected; } + #region Hotkey private void SetHotkey(string hotkeyStr, EventHandler action) @@ -624,7 +630,8 @@ private void SetHotkey(HotkeyModel hotkey, EventHandler action) catch (Exception) { string errorMsg = - string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr); + string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), + hotkeyStr); MessageBox.Show(errorMsg); } } @@ -674,7 +681,6 @@ private void OnHotkey(object sender, HotkeyEventArgs e) { if (!ShouldIgnoreHotkeys()) { - if (_settings.LastQueryMode == LastQueryMode.Empty) { ChangeQueryText(string.Empty); @@ -724,6 +730,7 @@ public void Save() _saved = true; } } + /// /// To avoid deadlock, this method should not called from main thread /// @@ -787,7 +794,6 @@ public void UpdateResultView(List list, PluginMetadata metadata, Query o { Results.AddResults(list, metadata.ID); } - } #endregion From 691c2b06f9f3e156d851cecef3d351237a794234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 10 Jan 2021 11:14:00 +0800 Subject: [PATCH 54/62] increase edittime boundary --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index fc77327ccf2..932d6fc4bc2 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -336,7 +336,7 @@ public void Update(List newItems, CancellationToken token = def if (Count == 0 && newItems.Count == 0 || _token.IsCancellationRequested) return; - if (editTime < 5 || newItems.Count < 30) + if (editTime < 10 || newItems.Count < 30) { if (Count != 0) ClearItems(); AddRange(newItems); From 3892b59724d7db36adb2725b05abbc2ccfd12677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 13 Jan 2021 09:25:46 +0800 Subject: [PATCH 55/62] Remove extra null check (use string.iswhitespaceornull) --- Flow.Launcher/ViewModel/MainViewModel.cs | 165 +++++++++++------------ 1 file changed, 81 insertions(+), 84 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ac63f0cc33d..0152cd854ba 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -94,7 +94,7 @@ private void RegisterResultsUpdatedEvent() { foreach (var pair in PluginManager.GetPluginsForInterface()) { - var plugin = (IResultUpdated) pair.Plugin; + var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); @@ -381,7 +381,7 @@ private void QueryHistory() Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), IcoPath = "Images\\history.png", - OriginQuery = new Query {RawQuery = h.Query}, + OriginQuery = new Query { RawQuery = h.Query }, Action = _ => { SelectedResults = Results; @@ -409,105 +409,102 @@ private void QueryHistory() private void QueryResults() { - if (!string.IsNullOrEmpty(QueryText)) + _updateSource?.Cancel(); + + if (string.IsNullOrWhiteSpace(QueryText)) { - _updateSource?.Cancel(); - _updateSource?.Dispose(); + Results.Clear(); + Results.Visbility = Visibility.Collapsed; + return; + } + + _updateSource?.Dispose(); + + var currentUpdateSource = new CancellationTokenSource(); + _updateSource = currentUpdateSource; + var currentCancellationToken = _updateSource.Token; + _updateToken = currentCancellationToken; + + ProgressBarVisibility = Visibility.Hidden; + _isQueryRunning = true; + + var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins); + + // handle the exclusiveness of plugin using action keyword + RemoveOldQueryResults(query); - var currentUpdateSource = new CancellationTokenSource(); - _updateSource = currentUpdateSource; - var currentCancellationToken = _updateSource.Token; - _updateToken = currentCancellationToken; + _lastQuery = query; - ProgressBarVisibility = Visibility.Hidden; - _isQueryRunning = true; - var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins); - if (query != null) + var plugins = PluginManager.ValidPluginsForQuery(query); + + Task.Run(async () => + { + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { - // handle the exclusiveness of plugin using action keyword - RemoveOldQueryResults(query); + // Wait 45 millisecond for query change in global query + // if query changes, return so that it won't be calculated + await Task.Delay(45, currentCancellationToken); + if (currentCancellationToken.IsCancellationRequested) + return; + } - _lastQuery = query; + _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => + { + // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet + if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + { + ProgressBarVisibility = Visibility.Visible; + } + }, currentCancellationToken); - var plugins = PluginManager.ValidPluginsForQuery(query); - Task.Run(async () => + // so looping will stop once it was cancelled + var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; + try + { + Parallel.ForEach(plugins, parallelOptions, plugin => + { + if (!plugin.Metadata.Disabled) { - if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) - { - // Wait 45 millisecond for query change in global query - // if query changes, return so that it won't be calculated - await Task.Delay(45, currentCancellationToken); - if (currentCancellationToken.IsCancellationRequested) - return; - } - - _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => - { - // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet - if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) - { - ProgressBarVisibility = Visibility.Visible; - } - }, currentCancellationToken); - - // so looping will stop once it was cancelled - var parallelOptions = new ParallelOptions {CancellationToken = currentCancellationToken}; try { - Parallel.ForEach(plugins, parallelOptions, plugin => - { - if (!plugin.Metadata.Disabled) - { - try - { - var results = PluginManager.QueryForPlugin(plugin, query); - _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, - query, - currentCancellationToken)); - } - catch (Exception e) - { - Log.Exception("MainViewModel", - $"Exception when querying the plugin {plugin.Metadata.Name}", e, - "QueryResults"); - } - } - }); + var results = PluginManager.QueryForPlugin(plugin, query); + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, + query, + currentCancellationToken)); } - catch (OperationCanceledException) + catch (Exception e) { - // nothing to do here + Log.Exception("MainViewModel", + $"Exception when querying the plugin {plugin.Metadata.Name}", e, + "QueryResults"); } + } + }); + } + catch (OperationCanceledException) + { + return; + // nothing to do here + } - if (currentCancellationToken.IsCancellationRequested) - return; + if (currentCancellationToken.IsCancellationRequested) + return; - // this should happen once after all queries are done so progress bar should continue - // until the end of all querying - _isQueryRunning = false; - if (!currentCancellationToken.IsCancellationRequested) - { - // update to hidden if this is still the current query - ProgressBarVisibility = Visibility.Hidden; - } - }, currentCancellationToken) - .ContinueWith( - t => - { - Log.Exception("MainViewModel", "Error when querying plugins", - t.Exception?.InnerException, - "QueryResults"); - }, TaskContinuationOptions.OnlyOnFaulted); + // this should happen once after all queries are done so progress bar should continue + // until the end of all querying + _isQueryRunning = false; + if (!currentCancellationToken.IsCancellationRequested) + { + // update to hidden if this is still the current query + ProgressBarVisibility = Visibility.Hidden; } - } - else - { - _updateSource?.Cancel(); - Results.Clear(); - Results.Visbility = Visibility.Collapsed; - } + }, currentCancellationToken) + .ContinueWith(t => Log.Exception("MainViewModel", "Error when querying plugins", t.Exception?.InnerException, "QueryResults") + , TaskContinuationOptions.OnlyOnFaulted); + } + private void RemoveOldQueryResults(Query query) { string lastKeyword = _lastQuery.ActionKeyword; From c39dfab6e55569a5538b1e37401a26bfc9bd7008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 13 Jan 2021 09:43:15 +0800 Subject: [PATCH 56/62] fix priority scoring --- Flow.Launcher/ViewModel/MainViewModel.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 0d79a55ec9a..cdac8f90dd6 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -755,15 +755,19 @@ public void UpdateResultView(IEnumerable resultsForUpdates) #endif - foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) + foreach (var metaResults in resultsForUpdates) { - if (_topMostRecord.IsTopMost(result)) - { - result.Score = int.MaxValue; - } - else + foreach (var result in metaResults.Results) { - result.Score += _userSelectedRecord.GetSelectedCount(result) * 5; + if (_topMostRecord.IsTopMost(result)) + { + result.Score = int.MaxValue; + } + else + { + var priorityScore = metaResults.Metadata.Priority * 50; + result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore; + } } } From 3d69998bc3fa2e66b53c1fae04a3d865daedcc7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Thu, 14 Jan 2021 19:54:37 +0800 Subject: [PATCH 57/62] fix potential duplicate item due to IResultUpdate event --- Flow.Launcher/ViewModel/MainViewModel.cs | 123 ++++++++++++----------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index cdac8f90dd6..233611be0cb 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -94,7 +94,7 @@ private void RegisterResultsUpdatedEvent() { foreach (var pair in PluginManager.GetPluginsForInterface()) { - var plugin = (IResultUpdated)pair.Plugin; + var plugin = (IResultUpdated) pair.Plugin; plugin.ResultsUpdated += (s, e) => { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); @@ -113,11 +113,18 @@ private void RegisterViewUpdate() async Task updateAction() { + var queue = new Dictionary(); while (await _resultsUpdateQueue.OutputAvailableAsync()) { + queue.Clear(); await Task.Delay(20); - _resultsUpdateQueue.TryReceiveAll(out var queue); - UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); + while (_resultsUpdateQueue.TryReceive(out var item)) + { + if (!item.Token.IsCancellationRequested) + queue[item.ID] = item; + } + + UpdateResultView(queue.Values); } } @@ -381,7 +388,7 @@ private void QueryHistory() Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), IcoPath = "Images\\history.png", - OriginQuery = new Query { RawQuery = h.Query }, + OriginQuery = new Query {RawQuery = h.Query}, Action = _ => { SelectedResults = Results; @@ -438,70 +445,70 @@ private void QueryResults() var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => - { - if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) - { - // Wait 45 millisecond for query change in global query - // if query changes, return so that it won't be calculated - await Task.Delay(45, currentCancellationToken); - if (currentCancellationToken.IsCancellationRequested) - return; - } - - _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => { - // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet - if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { - ProgressBarVisibility = Visibility.Visible; + // Wait 45 millisecond for query change in global query + // if query changes, return so that it won't be calculated + await Task.Delay(45, currentCancellationToken); + if (currentCancellationToken.IsCancellationRequested) + return; } - }, currentCancellationToken); - // so looping will stop once it was cancelled - var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; - try - { - Parallel.ForEach(plugins, parallelOptions, plugin => + _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => { - if (!plugin.Metadata.Disabled) + // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet + if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) { - try - { - var results = PluginManager.QueryForPlugin(plugin, query); - _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, - query, - currentCancellationToken)); - } - catch (Exception e) - { - Log.Exception("MainViewModel", - $"Exception when querying the plugin {plugin.Metadata.Name}", e, - "QueryResults"); - } + ProgressBarVisibility = Visibility.Visible; } - }); - } - catch (OperationCanceledException) - { - return; - // nothing to do here - } + }, currentCancellationToken); - if (currentCancellationToken.IsCancellationRequested) - return; + // so looping will stop once it was cancelled + var parallelOptions = new ParallelOptions {CancellationToken = currentCancellationToken}; + try + { + Parallel.ForEach(plugins, parallelOptions, plugin => + { + if (!plugin.Metadata.Disabled) + { + try + { + var results = PluginManager.QueryForPlugin(plugin, query); + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, + query, + currentCancellationToken)); + } + catch (Exception e) + { + Log.Exception("MainViewModel", + $"Exception when querying the plugin {plugin.Metadata.Name}", e, + "QueryResults"); + } + } + }); + } + catch (OperationCanceledException) + { + return; + // nothing to do here + } - // this should happen once after all queries are done so progress bar should continue - // until the end of all querying - _isQueryRunning = false; - if (!currentCancellationToken.IsCancellationRequested) - { - // update to hidden if this is still the current query - ProgressBarVisibility = Visibility.Hidden; - } - }, currentCancellationToken) - .ContinueWith(t => Log.Exception("MainViewModel", "Error when querying plugins", t.Exception?.InnerException, "QueryResults") - , TaskContinuationOptions.OnlyOnFaulted); + if (currentCancellationToken.IsCancellationRequested) + return; + // this should happen once after all queries are done so progress bar should continue + // until the end of all querying + _isQueryRunning = false; + if (!currentCancellationToken.IsCancellationRequested) + { + // update to hidden if this is still the current query + ProgressBarVisibility = Visibility.Hidden; + } + }, currentCancellationToken) + .ContinueWith(t => Log.Exception("MainViewModel", "Error when querying plugins", + t.Exception?.InnerException, "QueryResults") + , TaskContinuationOptions.OnlyOnFaulted); } From 3abc77f391d48b0ba15e1c65b4e9722149470b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:05:31 +0800 Subject: [PATCH 58/62] Merge remote-tracking branch 'upstream/dev' into RenderImprovement # Conflicts: # Flow.Launcher/ViewModel/MainViewModel.cs --- Flow.Launcher/ViewModel/MainViewModel.cs | 87 ++++++++---------------- 1 file changed, 28 insertions(+), 59 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 139d891694a..35a7302f426 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -244,7 +244,7 @@ private void InitializeKeyCommands() public string QueryText { - get { return _queryText; } + get => _queryText; set { _queryText = value; @@ -444,28 +444,26 @@ private void QueryResults() var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => - { - if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) - { - // Wait 45 millisecond for query change in global query - // if query changes, return so that it won't be calculated - await Task.Delay(45, currentCancellationToken); - if (currentCancellationToken.IsCancellationRequested) - return; - } - - _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => { - // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet - if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { - ProgressBarVisibility = Visibility.Visible; + // Wait 45 millisecond for query change in global query + // if query changes, return so that it won't be calculated + await Task.Delay(45, currentCancellationToken); + if (currentCancellationToken.IsCancellationRequested) + return; } - }, currentCancellationToken); - var plugins = PluginManager.ValidPluginsForQuery(query); - Task.Run(async () => - { + _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => + { + // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet + if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + { + ProgressBarVisibility = Visibility.Visible; + } + }, currentCancellationToken); + + var plugins = PluginManager.ValidPluginsForQuery(query); // so looping will stop once it was cancelled Task[] tasks = new Task[plugins.Count]; @@ -475,7 +473,7 @@ private void QueryResults() { if (!plugins[i].Metadata.Disabled) { - tasks[i] = QueryTask(plugins[i], query, currentCancellationToken); + tasks[i] = QueryTask(plugins[i]); } else { @@ -491,6 +489,9 @@ private void QueryResults() // nothing to do here } + if (currentCancellationToken.IsCancellationRequested) + return; + // this should happen once after all queries are done so progress bar should continue // until the end of all querying _isQueryRunning = false; @@ -501,28 +502,20 @@ private void QueryResults() } // Local function - async Task QueryTask(PluginPair plugin, Query query, CancellationToken token) + async Task QueryTask(PluginPair plugin) { // Since it is wrapped within a Task.Run, the synchronous context is null // Task.Yield will force it to run in ThreadPool await Task.Yield(); - var results = await PluginManager.QueryForPlugin(plugin, query, token); + var results = await PluginManager.QueryForPlugin(plugin, query, currentCancellationToken); if (!currentCancellationToken.IsCancellationRequested) - UpdateResultView(results, plugin.Metadata, query); + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query, + currentCancellationToken)); } - }, currentCancellationToken).ContinueWith( - t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception), + }, currentCancellationToken) + .ContinueWith(t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception), TaskContinuationOptions.OnlyOnFaulted); - - - else - - { - Results.Clear(); - Results.Visbility = Visibility.Collapsed; - } - } } @@ -786,7 +779,7 @@ public void UpdateResultView(IEnumerable resultsForUpdates) } else { - var priorityScore = metaResults.Metadata.Priority * 50; + var priorityScore = metaResults.Metadata.Priority * 150; result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore; } } @@ -795,30 +788,6 @@ public void UpdateResultView(IEnumerable resultsForUpdates) Results.AddResults(resultsForUpdates, token); } - /// U - /// To avoid deadlock, this method should not called from main thread - /// - public void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) - { - foreach (var result in list) - { - if (_topMostRecord.IsTopMost(result)) - { - result.Score = int.MaxValue; - } - else - { - var priorityScore = metadata.Priority * 150; - result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore; - } - } - - if (originQuery.RawQuery == _lastQuery.RawQuery) - { - Results.AddResults(list, metadata.ID); - } - } - #endregion } } \ No newline at end of file From 1071f75633305e41d27f155a9f60d359604f386d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:06:57 +0800 Subject: [PATCH 59/62] Remove unused using --- Flow.Launcher/ViewModel/MainViewModel.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 35a7302f426..2dcbfc00490 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,9 +17,6 @@ using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; -using System.Windows.Media; -using Flow.Launcher.Infrastructure.Image; -using System.Collections.Concurrent; using Flow.Launcher.Infrastructure.Logger; using System.Threading.Tasks.Dataflow; From 501124609038dea1e4d06f6194edbdd5f71fbb5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:10:36 +0800 Subject: [PATCH 60/62] Add scoring to ContextMenu search --- Flow.Launcher/ViewModel/MainViewModel.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 2dcbfc00490..454d934a338 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -354,9 +354,20 @@ private void QueryContextMenu() { var filtered = results.Where ( - r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() - || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() - ).ToList(); + r => + { + var match = StringMatcher.FuzzySearch(query, r.Title); + if (!match.IsSearchPrecisionScoreMet()) + { + match = StringMatcher.FuzzySearch(query, r.SubTitle); + } + + if (!match.IsSearchPrecisionScoreMet()) return false; + + r.Score = match.Score; + return true; + + }).ToList(); ContextMenu.AddResults(filtered, id); } else From e2e3f4a365acef01ca9fc182bc91543c49f15adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:21:35 +0800 Subject: [PATCH 61/62] remove extra query building due to merge --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 454d934a338..8195c745aa5 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -469,9 +469,6 @@ private void QueryResults() } }, currentCancellationToken); - var plugins = PluginManager.ValidPluginsForQuery(query); - // so looping will stop once it was cancelled - Task[] tasks = new Task[plugins.Count]; try { From 89443bace76f44fb0551f3f63d70a8779868b92a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 20 Jan 2021 06:34:26 +1100 Subject: [PATCH 62/62] fix formatting --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 1 - Flow.Launcher/ViewModel/ResultViewModel.cs | 4 ---- Flow.Launcher/ViewModel/ResultsForUpdate.cs | 1 - Flow.Launcher/ViewModel/ResultsViewModel.cs | 15 --------------- 4 files changed, 21 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 73f565a3336..ac333d567b8 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -39,7 +39,6 @@ public static void Initialize() var usage = LoadStorageToConcurrentDictionary(); - foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) { ImageSource img = new BitmapImage(new Uri(icon)); diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index fa0c51a6576..c91bbb1074f 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -1,14 +1,11 @@ using System; -using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; -using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using Microsoft.FSharp.Core; namespace Flow.Launcher.ViewModel { @@ -128,7 +125,6 @@ public override bool Equals(object obj) } } - public override int GetHashCode() { return Result.GetHashCode(); diff --git a/Flow.Launcher/ViewModel/ResultsForUpdate.cs b/Flow.Launcher/ViewModel/ResultsForUpdate.cs index 2257d35b0d0..be48f53c16b 100644 --- a/Flow.Launcher/ViewModel/ResultsForUpdate.cs +++ b/Flow.Launcher/ViewModel/ResultsForUpdate.cs @@ -23,7 +23,6 @@ public ResultsForUpdate(List results, string resultID, CancellationToken Token = token; } - public ResultsForUpdate(List results, PluginMetadata metadata, Query query, CancellationToken token) { Results = results; diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 932d6fc4bc2..1b8dd602dbc 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.ComponentModel; -using System.Configuration; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -11,7 +9,6 @@ using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; -using System.Windows.Forms; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -137,13 +134,11 @@ public void KeepResultsExcept(PluginMetadata metadata) Results.Update(Results.Where(r => r.Result.PluginID != metadata.ID).ToList()); } - /// /// To avoid deadlock, this method should not called from main thread /// public void AddResults(List newRawResults, string resultId) { - lock (_collectionLock) { var newResults = NewResults(newRawResults, resultId); @@ -193,8 +188,6 @@ public void AddResults(IEnumerable resultsForUpdates, Cancella Results.Update(newResults, token); if (Results.Any()) SelectedItem = Results[0]; - - } switch (Visbility) @@ -212,7 +205,6 @@ public void AddResults(IEnumerable resultsForUpdates, Cancella } - private List NewResults(List newRawResults, string resultId) { if (newRawResults.Count == 0) @@ -222,8 +214,6 @@ private List NewResults(List newRawResults, string resu var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList(); - - return results.Where(r => r.Result.PluginID != resultId) .Concat(results.Intersect(newResults).Union(newResults)) .OrderByDescending(r => r.Result.Score) @@ -245,7 +235,6 @@ private List NewResults(IEnumerable resultsFo } #endregion - #region FormattedText Dependency Property public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached( "FormattedText", @@ -279,7 +268,6 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP public class ResultCollection : ObservableCollection { - private long editTime = 0; private bool _suppressNotifying = false; @@ -323,9 +311,6 @@ public void RemoveAll() ClearItems(); } - - - /// /// Update the results collection with new results, try to keep identical results ///