Skip to content

Commit 5dd12d2

Browse files
authored
Merge pull request #301 from taooceros/ExplorerPathAsync
Add Cancellation token for file system enumeration
2 parents fc78c0c + 163bfa3 commit 5dd12d2

File tree

6 files changed

+103
-110
lines changed

6 files changed

+103
-110
lines changed

Flow.Launcher.Test/Plugins/ExplorerTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ private async Task<List<Result>> MethodWindowsIndexSearchReturnsZeroResultsAsync
2424
return new List<Result>();
2525
}
2626

27-
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString)
27+
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString, CancellationToken token)
2828
{
2929
return new List<Result>
3030
{

Flow.Launcher/ViewModel/ResultsViewModel.cs

Lines changed: 42 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -139,52 +139,28 @@ public void KeepResultsExcept(PluginMetadata metadata)
139139
/// </summary>
140140
public void AddResults(List<Result> newRawResults, string resultId)
141141
{
142-
lock (_collectionLock)
143-
{
144-
var newResults = NewResults(newRawResults, resultId);
145-
146-
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf
147-
// fix selected index flow
148-
var updateTask = Task.Run(() =>
149-
{
150-
// update UI in one run, so it can avoid UI flickering
151-
152-
Results.Update(newResults);
153-
if (Results.Any())
154-
SelectedItem = Results[0];
155-
});
156-
if (!updateTask.Wait(300))
157-
{
158-
updateTask.Dispose();
159-
throw new TimeoutException("Update result use too much time.");
160-
}
142+
var newResults = NewResults(newRawResults, resultId);
161143

162-
}
163-
164-
if (Visbility != Visibility.Visible && Results.Count > 0)
165-
{
166-
Margin = new Thickness { Top = 8 };
167-
SelectedIndex = 0;
168-
Visbility = Visibility.Visible;
169-
}
170-
else
171-
{
172-
Margin = new Thickness { Top = 0 };
173-
Visbility = Visibility.Collapsed;
174-
}
144+
UpdateResults(newResults);
175145
}
176146
/// <summary>
177147
/// To avoid deadlock, this method should not called from main thread
178148
/// </summary>
179149
public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, CancellationToken token)
180150
{
181151
var newResults = NewResults(resultsForUpdates);
152+
182153
if (token.IsCancellationRequested)
183154
return;
155+
156+
UpdateResults(newResults, token);
157+
}
158+
159+
private void UpdateResults(List<ResultViewModel> newResults, CancellationToken token = default)
160+
{
184161
lock (_collectionLock)
185162
{
186163
// update UI in one run, so it can avoid UI flickering
187-
188164
Results.Update(newResults, token);
189165
if (Results.Any())
190166
SelectedItem = Results[0];
@@ -202,7 +178,6 @@ public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, Cancella
202178
Visbility = Visibility.Collapsed;
203179
break;
204180
}
205-
206181
}
207182

208183
private List<ResultViewModel> NewResults(List<Result> newRawResults, string resultId)
@@ -212,10 +187,10 @@ private List<ResultViewModel> NewResults(List<Result> newRawResults, string resu
212187

213188
var results = Results as IEnumerable<ResultViewModel>;
214189

215-
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList();
190+
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings));
216191

217192
return results.Where(r => r.Result.PluginID != resultId)
218-
.Concat(results.Intersect(newResults).Union(newResults))
193+
.Concat(newResults)
219194
.OrderByDescending(r => r.Result.Score)
220195
.ToList();
221196
}
@@ -228,8 +203,7 @@ private List<ResultViewModel> NewResults(IEnumerable<ResultsForUpdate> resultsFo
228203
var results = Results as IEnumerable<ResultViewModel>;
229204

230205
return results.Where(r => r != null && !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID))
231-
.Concat(
232-
resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
206+
.Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
233207
.OrderByDescending(rv => rv.Result.Score)
234208
.ToList();
235209
}
@@ -266,49 +240,50 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP
266240
}
267241
#endregion
268242

269-
public class ResultCollection : ObservableCollection<ResultViewModel>
243+
public class ResultCollection : List<ResultViewModel>, INotifyCollectionChanged
270244
{
271245
private long editTime = 0;
272246

273-
private bool _suppressNotifying = false;
274-
275247
private CancellationToken _token;
276248

277-
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
249+
public event NotifyCollectionChangedEventHandler CollectionChanged;
250+
251+
252+
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
278253
{
279-
if (!_suppressNotifying)
280-
{
281-
base.OnCollectionChanged(e);
282-
}
254+
CollectionChanged?.Invoke(this, e);
283255
}
284256

285-
public void BulkAddRange(IEnumerable<ResultViewModel> resultViews)
257+
public void BulkAddAll(List<ResultViewModel> resultViews)
286258
{
287-
// suppress notifying before adding all element
288-
_suppressNotifying = true;
289-
foreach (var item in resultViews)
290-
{
291-
Add(item);
292-
}
293-
_suppressNotifying = false;
294-
// manually update event
295-
// wpf use directx / double buffered already, so just reset all won't cause ui flickering
259+
AddRange(resultViews);
260+
261+
// can return because the list will be cleared next time updated, which include a reset event
296262
if (_token.IsCancellationRequested)
297263
return;
264+
265+
// manually update event
266+
// wpf use directx / double buffered already, so just reset all won't cause ui flickering
298267
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
299268
}
300-
public void AddRange(IEnumerable<ResultViewModel> Items)
269+
private void AddAll(List<ResultViewModel> Items)
301270
{
302-
foreach (var item in Items)
271+
for (int i = 0; i < Items.Count; i++)
303272
{
273+
var item = Items[i];
304274
if (_token.IsCancellationRequested)
305275
return;
306276
Add(item);
277+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, i));
307278
}
308279
}
309-
public void RemoveAll()
280+
public void RemoveAll(int Capacity = 512)
310281
{
311-
ClearItems();
282+
Clear();
283+
if (this.Capacity > 8000 && Capacity < this.Capacity)
284+
this.Capacity = Capacity;
285+
286+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
312287
}
313288

314289
/// <summary>
@@ -323,15 +298,19 @@ public void Update(List<ResultViewModel> newItems, CancellationToken token = def
323298

324299
if (editTime < 10 || newItems.Count < 30)
325300
{
326-
if (Count != 0) ClearItems();
327-
AddRange(newItems);
301+
if (Count != 0) RemoveAll(newItems.Count);
302+
AddAll(newItems);
328303
editTime++;
329304
return;
330305
}
331306
else
332307
{
333308
Clear();
334-
BulkAddRange(newItems);
309+
BulkAddAll(newItems);
310+
if (Capacity > 8000 && newItems.Count < 3000)
311+
{
312+
Capacity = newItems.Count;
313+
}
335314
editTime++;
336315
}
337316
}

Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.IO;
66
using System.Linq;
7+
using System.Threading;
78

89
namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
910
{
@@ -16,14 +17,18 @@ public DirectoryInfoSearch(PluginInitContext context)
1617
resultManager = new ResultManager(context);
1718
}
1819

19-
internal List<Result> TopLevelDirectorySearch(Query query, string search)
20+
internal List<Result> TopLevelDirectorySearch(Query query, string search, CancellationToken token)
2021
{
2122
var criteria = ConstructSearchCriteria(search);
2223

23-
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > search.LastIndexOf(Constants.DirectorySeperator))
24-
return DirectorySearch(SearchOption.AllDirectories, query, search, criteria);
24+
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) >
25+
search.LastIndexOf(Constants.DirectorySeperator))
26+
return DirectorySearch(new EnumerationOptions
27+
{
28+
RecurseSubdirectories = true
29+
}, query, search, criteria, token);
2530

26-
return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria);
31+
return DirectorySearch(new EnumerationOptions(), query, search, criteria, token); // null will be passed as default
2732
}
2833

2934
public string ConstructSearchCriteria(string search)
@@ -45,7 +50,8 @@ public string ConstructSearchCriteria(string search)
4550
return incompleteName;
4651
}
4752

48-
private List<Result> DirectorySearch(SearchOption searchOption, Query query, string search, string searchCriteria)
53+
private List<Result> DirectorySearch(EnumerationOptions enumerationOption, Query query, string search,
54+
string searchCriteria, CancellationToken token)
4955
{
5056
var results = new List<Result>();
5157

@@ -58,38 +64,38 @@ private List<Result> DirectorySearch(SearchOption searchOption, Query query, str
5864
{
5965
var directoryInfo = new System.IO.DirectoryInfo(path);
6066

61-
foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, searchOption))
67+
foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption))
6268
{
63-
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
64-
6569
if (fileSystemInfo is System.IO.DirectoryInfo)
6670
{
67-
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query, true, false));
71+
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName,
72+
fileSystemInfo.FullName, query, true, false));
6873
}
6974
else
7075
{
7176
fileList.Add(resultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false));
7277
}
78+
79+
token.ThrowIfCancellationRequested();
7380
}
7481
}
7582
catch (Exception e)
7683
{
77-
if (e is UnauthorizedAccessException || e is ArgumentException)
78-
{
79-
results.Add(new Result { Title = e.Message, Score = 501 });
84+
if (!(e is ArgumentException))
85+
throw e;
86+
87+
results.Add(new Result {Title = e.Message, Score = 501});
8088

81-
return results;
82-
}
89+
return results;
8390

8491
#if DEBUG // Please investigate and handle error from DirectoryInfo search
85-
throw e;
8692
#else
8793
Log.Exception($"|Flow.Launcher.Plugin.Explorer.DirectoryInfoSearch|Error from performing DirectoryInfoSearch", e);
88-
#endif
94+
#endif
8995
}
9096

91-
// Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
97+
// Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
9298
return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList();
9399
}
94100
}
95-
}
101+
}

Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,31 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
66
{
77
public class QuickFolderAccess
88
{
9-
internal List<Result> FolderListMatched(Query query, List<FolderLink> folderLinks, PluginInitContext context)
9+
private readonly ResultManager resultManager;
10+
11+
public QuickFolderAccess(PluginInitContext context)
12+
{
13+
resultManager = new ResultManager(context);
14+
}
15+
16+
internal List<Result> FolderListMatched(Query query, List<FolderLink> folderLinks)
1017
{
1118
if (string.IsNullOrEmpty(query.Search))
1219
return new List<Result>();
1320

1421
string search = query.Search.ToLower();
15-
16-
var queriedFolderLinks = folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
22+
23+
var queriedFolderLinks =
24+
folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
1725

1826
return queriedFolderLinks.Select(item =>
19-
new ResultManager(context)
20-
.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
21-
.ToList();
27+
resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
28+
.ToList();
2229
}
2330

24-
internal List<Result> FolderListAll(Query query, List<FolderLink> folderLinks, PluginInitContext context)
31+
internal List<Result> FolderListAll(Query query, List<FolderLink> folderLinks)
2532
=> folderLinks
26-
.Select(item =>
27-
new ResultManager(context).CreateFolderResult(item.Nickname, item.Path, item.Path, query))
33+
.Select(item => resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
2834
.ToList();
2935
}
3036
}

0 commit comments

Comments
 (0)