mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #301 from taooceros/ExplorerPathAsync
Add Cancellation token for file system enumeration
This commit is contained in:
commit
5dd12d212b
6 changed files with 103 additions and 110 deletions
|
|
@ -24,7 +24,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
return new List<Result>();
|
||||
}
|
||||
|
||||
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString)
|
||||
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString, CancellationToken token)
|
||||
{
|
||||
return new List<Result>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -139,39 +139,9 @@ namespace Flow.Launcher.ViewModel
|
|||
/// </summary>
|
||||
public void AddResults(List<Result> newRawResults, string resultId)
|
||||
{
|
||||
lock (_collectionLock)
|
||||
{
|
||||
var newResults = NewResults(newRawResults, resultId);
|
||||
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
|
||||
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.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
UpdateResults(newResults);
|
||||
}
|
||||
/// <summary>
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
|
|
@ -179,12 +149,18 @@ namespace Flow.Launcher.ViewModel
|
|||
public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, CancellationToken token)
|
||||
{
|
||||
var newResults = NewResults(resultsForUpdates);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
UpdateResults(newResults, token);
|
||||
}
|
||||
|
||||
private void UpdateResults(List<ResultViewModel> newResults, CancellationToken token = default)
|
||||
{
|
||||
lock (_collectionLock)
|
||||
{
|
||||
// update UI in one run, so it can avoid UI flickering
|
||||
|
||||
Results.Update(newResults, token);
|
||||
if (Results.Any())
|
||||
SelectedItem = Results[0];
|
||||
|
|
@ -202,7 +178,6 @@ namespace Flow.Launcher.ViewModel
|
|||
Visbility = Visibility.Collapsed;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<ResultViewModel> NewResults(List<Result> newRawResults, string resultId)
|
||||
|
|
@ -212,10 +187,10 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
var results = Results as IEnumerable<ResultViewModel>;
|
||||
|
||||
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList();
|
||||
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings));
|
||||
|
||||
return results.Where(r => r.Result.PluginID != resultId)
|
||||
.Concat(results.Intersect(newResults).Union(newResults))
|
||||
.Concat(newResults)
|
||||
.OrderByDescending(r => r.Result.Score)
|
||||
.ToList();
|
||||
}
|
||||
|
|
@ -228,8 +203,7 @@ namespace Flow.Launcher.ViewModel
|
|||
var results = Results as IEnumerable<ResultViewModel>;
|
||||
|
||||
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)))
|
||||
.Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
|
||||
.OrderByDescending(rv => rv.Result.Score)
|
||||
.ToList();
|
||||
}
|
||||
|
|
@ -266,49 +240,50 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
#endregion
|
||||
|
||||
public class ResultCollection : ObservableCollection<ResultViewModel>
|
||||
public class ResultCollection : List<ResultViewModel>, INotifyCollectionChanged
|
||||
{
|
||||
private long editTime = 0;
|
||||
|
||||
private bool _suppressNotifying = false;
|
||||
|
||||
private CancellationToken _token;
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
|
||||
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (!_suppressNotifying)
|
||||
{
|
||||
base.OnCollectionChanged(e);
|
||||
}
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public void BulkAddRange(IEnumerable<ResultViewModel> resultViews)
|
||||
public void BulkAddAll(List<ResultViewModel> 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
|
||||
AddRange(resultViews);
|
||||
|
||||
// can return because the list will be cleared next time updated, which include a reset event
|
||||
if (_token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// manually update event
|
||||
// wpf use directx / double buffered already, so just reset all won't cause ui flickering
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
public void AddRange(IEnumerable<ResultViewModel> Items)
|
||||
private void AddAll(List<ResultViewModel> Items)
|
||||
{
|
||||
foreach (var item in Items)
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
var item = Items[i];
|
||||
if (_token.IsCancellationRequested)
|
||||
return;
|
||||
Add(item);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, i));
|
||||
}
|
||||
}
|
||||
public void RemoveAll()
|
||||
public void RemoveAll(int Capacity = 512)
|
||||
{
|
||||
ClearItems();
|
||||
Clear();
|
||||
if (this.Capacity > 8000 && Capacity < this.Capacity)
|
||||
this.Capacity = Capacity;
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -323,15 +298,19 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
if (editTime < 10 || newItems.Count < 30)
|
||||
{
|
||||
if (Count != 0) ClearItems();
|
||||
AddRange(newItems);
|
||||
if (Count != 0) RemoveAll(newItems.Count);
|
||||
AddAll(newItems);
|
||||
editTime++;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Clear();
|
||||
BulkAddRange(newItems);
|
||||
BulkAddAll(newItems);
|
||||
if (Capacity > 8000 && newItems.Count < 3000)
|
||||
{
|
||||
Capacity = newItems.Count;
|
||||
}
|
||||
editTime++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
||||
{
|
||||
|
|
@ -16,14 +17,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
resultManager = new ResultManager(context);
|
||||
}
|
||||
|
||||
internal List<Result> TopLevelDirectorySearch(Query query, string search)
|
||||
internal List<Result> TopLevelDirectorySearch(Query query, string search, CancellationToken token)
|
||||
{
|
||||
var criteria = ConstructSearchCriteria(search);
|
||||
|
||||
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > search.LastIndexOf(Constants.DirectorySeperator))
|
||||
return DirectorySearch(SearchOption.AllDirectories, query, search, criteria);
|
||||
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) >
|
||||
search.LastIndexOf(Constants.DirectorySeperator))
|
||||
return DirectorySearch(new EnumerationOptions
|
||||
{
|
||||
RecurseSubdirectories = true
|
||||
}, query, search, criteria, token);
|
||||
|
||||
return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria);
|
||||
return DirectorySearch(new EnumerationOptions(), query, search, criteria, token); // null will be passed as default
|
||||
}
|
||||
|
||||
public string ConstructSearchCriteria(string search)
|
||||
|
|
@ -45,7 +50,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
return incompleteName;
|
||||
}
|
||||
|
||||
private List<Result> DirectorySearch(SearchOption searchOption, Query query, string search, string searchCriteria)
|
||||
private List<Result> DirectorySearch(EnumerationOptions enumerationOption, Query query, string search,
|
||||
string searchCriteria, CancellationToken token)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
|
||||
|
|
@ -58,38 +64,38 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
{
|
||||
var directoryInfo = new System.IO.DirectoryInfo(path);
|
||||
|
||||
foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, searchOption))
|
||||
foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption))
|
||||
{
|
||||
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
|
||||
|
||||
if (fileSystemInfo is System.IO.DirectoryInfo)
|
||||
{
|
||||
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query, true, false));
|
||||
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName,
|
||||
fileSystemInfo.FullName, query, true, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileList.Add(resultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false));
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is UnauthorizedAccessException || e is ArgumentException)
|
||||
{
|
||||
results.Add(new Result { Title = e.Message, Score = 501 });
|
||||
if (!(e is ArgumentException))
|
||||
throw e;
|
||||
|
||||
results.Add(new Result {Title = e.Message, Score = 501});
|
||||
|
||||
return results;
|
||||
}
|
||||
return results;
|
||||
|
||||
#if DEBUG // Please investigate and handle error from DirectoryInfo search
|
||||
throw e;
|
||||
#else
|
||||
Log.Exception($"|Flow.Launcher.Plugin.Explorer.DirectoryInfoSearch|Error from performing DirectoryInfoSearch", e);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
// Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
|
||||
// Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
|
||||
return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,25 +6,31 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
|
|||
{
|
||||
public class QuickFolderAccess
|
||||
{
|
||||
internal List<Result> FolderListMatched(Query query, List<FolderLink> folderLinks, PluginInitContext context)
|
||||
private readonly ResultManager resultManager;
|
||||
|
||||
public QuickFolderAccess(PluginInitContext context)
|
||||
{
|
||||
resultManager = new ResultManager(context);
|
||||
}
|
||||
|
||||
internal List<Result> FolderListMatched(Query query, List<FolderLink> folderLinks)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query.Search))
|
||||
return new List<Result>();
|
||||
|
||||
string search = query.Search.ToLower();
|
||||
|
||||
var queriedFolderLinks = folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var queriedFolderLinks =
|
||||
folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return queriedFolderLinks.Select(item =>
|
||||
new ResultManager(context)
|
||||
.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
|
||||
.ToList();
|
||||
resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
internal List<Result> FolderListAll(Query query, List<FolderLink> folderLinks, PluginInitContext context)
|
||||
internal List<Result> FolderListAll(Query query, List<FolderLink> folderLinks)
|
||||
=> folderLinks
|
||||
.Select(item =>
|
||||
new ResultManager(context).CreateFolderResult(item.Nickname, item.Path, item.Path, query))
|
||||
.Select(item => resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
private readonly IndexSearch indexSearch;
|
||||
|
||||
private readonly QuickFolderAccess quickFolderAccess = new QuickFolderAccess();
|
||||
private readonly QuickFolderAccess quickFolderAccess;
|
||||
|
||||
private readonly ResultManager resultManager;
|
||||
|
||||
|
|
@ -28,6 +28,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
indexSearch = new IndexSearch(context);
|
||||
resultManager = new ResultManager(context);
|
||||
this.settings = settings;
|
||||
quickFolderAccess = new QuickFolderAccess(context);
|
||||
}
|
||||
|
||||
internal async Task<List<Result>> SearchAsync(Query query, CancellationToken token)
|
||||
|
|
@ -40,12 +41,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
return await WindowsIndexFileContentSearchAsync(query, querySearch, token).ConfigureAwait(false);
|
||||
|
||||
// This allows the user to type the assigned action keyword and only see the list of quick folder links
|
||||
if (settings.QuickFolderAccessLinks.Count > 0
|
||||
&& query.ActionKeyword == settings.SearchActionKeyword
|
||||
&& string.IsNullOrEmpty(query.Search))
|
||||
return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context);
|
||||
if (string.IsNullOrEmpty(query.Search))
|
||||
return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks);
|
||||
|
||||
var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context);
|
||||
var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks);
|
||||
|
||||
if (quickFolderLinks.Count > 0)
|
||||
results.AddRange(quickFolderLinks);
|
||||
|
|
@ -78,15 +77,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
results.Add(resultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch));
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return null;
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
results.AddRange(await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync,
|
||||
DirectoryInfoClassSearch,
|
||||
useIndexSearch,
|
||||
query,
|
||||
locationPath,
|
||||
token).ConfigureAwait(false));
|
||||
var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync,
|
||||
DirectoryInfoClassSearch,
|
||||
useIndexSearch,
|
||||
query,
|
||||
locationPath,
|
||||
token).ConfigureAwait(false);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
results.AddRange(directoryResult);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
|
@ -110,23 +112,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
return actionKeyword == settings.FileContentSearchActionKeyword;
|
||||
}
|
||||
|
||||
private List<Result> DirectoryInfoClassSearch(Query query, string querySearch)
|
||||
private List<Result> DirectoryInfoClassSearch(Query query, string querySearch, CancellationToken token)
|
||||
{
|
||||
var directoryInfoSearch = new DirectoryInfoSearch(context);
|
||||
|
||||
return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch);
|
||||
return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch, token);
|
||||
}
|
||||
|
||||
public async Task<List<Result>> TopLevelDirectorySearchBehaviourAsync(
|
||||
Func<Query, string, CancellationToken, Task<List<Result>>> windowsIndexSearch,
|
||||
Func<Query, string, List<Result>> directoryInfoClassSearch,
|
||||
Func<Query, string, CancellationToken, List<Result>> directoryInfoClassSearch,
|
||||
bool useIndexSearch,
|
||||
Query query,
|
||||
string querySearchString,
|
||||
CancellationToken token)
|
||||
{
|
||||
if (!useIndexSearch)
|
||||
return directoryInfoClassSearch(query, querySearchString);
|
||||
return directoryInfoClassSearch(query, querySearchString, token);
|
||||
|
||||
return await windowsIndexSearch(query, querySearchString, token);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"Name": "Explorer",
|
||||
"Description": "Search and manage files and folders. Explorer utilises Windows Index Search",
|
||||
"Author": "Jeremy Wu",
|
||||
"Version": "1.4.1",
|
||||
"Version": "1.5.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",
|
||||
|
|
|
|||
Loading…
Reference in a new issue