Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Flow.Launcher.Test/Plugins/ExplorerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ private async Task<List<Result>> MethodWindowsIndexSearchReturnsZeroResultsAsync
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>
{
Expand Down
105 changes: 42 additions & 63 deletions Flow.Launcher/ViewModel/ResultsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,52 +139,28 @@ public void KeepResultsExcept(PluginMetadata metadata)
/// </summary>
public void AddResults(List<Result> newRawResults, string 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
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.");
}
var newResults = NewResults(newRawResults, resultId);

}

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@taooceros do you know if this comment is still relevant?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes, because only results from main result set will be added via resultUpdateQueue, the one for context menu and history is still added directly.
However, I am not sure the deadlock meaning here. Maybe just avoiding potential stunt of main ui due to lock machanism.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, thank you

/// </summary>
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];
Expand All @@ -202,7 +178,6 @@ public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, Cancella
Visbility = Visibility.Collapsed;
break;
}

}

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

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();
}
Expand All @@ -228,8 +203,7 @@ private List<ResultViewModel> NewResults(IEnumerable<ResultsForUpdate> resultsFo
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();
}
Expand Down Expand Up @@ -266,49 +240,50 @@ private static void FormattedTextPropertyChanged(DependencyObject d, DependencyP
}
#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>
Expand All @@ -323,15 +298,19 @@ public void Update(List<ResultViewModel> newItems, CancellationToken token = def

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++;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;

namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
{
Expand All @@ -16,14 +17,18 @@ public DirectoryInfoSearch(PluginInitContext context)
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)
Expand All @@ -45,7 +50,8 @@ public string ConstructSearchCriteria(string search)
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>();

Expand All @@ -58,38 +64,38 @@ private List<Result> DirectorySearch(SearchOption searchOption, Query query, str
{
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();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Loading