Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance: Optimized the recent files widget #10867

Merged
merged 5 commits into from
Dec 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/Files.App/Filesystem/RecentItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ public bool Equals(RecentItem other)
RecentPath == other.RecentPath;
}

public override int GetHashCode() => (LinkPath, RecentPath).GetHashCode();
public override bool Equals(object? o) => o is RecentItem other && Equals(other);

/**
* Strips a name from an extension while aware of some edge cases.
*
Expand Down
65 changes: 53 additions & 12 deletions src/Files.App/Filesystem/RecentItems.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,17 @@ public async Task UpdateRecentFilesAsync()
List<RecentItem> enumeratedFiles = await ListRecentFilesAsync();
if (enumeratedFiles is not null)
{
var recentFilesSnapshot = RecentFiles;

lock (recentFiles)
{
recentFiles.Clear();
recentFiles.AddRange(enumeratedFiles);
// do not sort here, enumeration order *is* the correct order since we get it from Quick Access
}

// todo: potentially optimize this and figure out if list changed by either (1) Add (2) Remove (3) Move
// this way the UI doesn't have to refresh the entire list everytime a change occurs
RecentFilesChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
var changedActionEventArgs = GetChangedActionEventArgs(recentFilesSnapshot, enumeratedFiles);
RecentFilesChanged?.Invoke(this, changedActionEventArgs);
}
}

Expand Down Expand Up @@ -205,6 +206,55 @@ public Task<bool> UnpinFromRecentFiles(RecentItem item)
}));
}

private NotifyCollectionChangedEventArgs GetChangedActionEventArgs(IReadOnlyList<RecentItem> oldItems, IList<RecentItem> newItems)
{
// a single item was added
if (newItems.Count == oldItems.Count + 1)
{
var differences = newItems.Except(oldItems);
if (differences.Take(2).Count() == 1)
{
return new(NotifyCollectionChangedAction.Add, newItems.First());
}
}
// a single item was removed
else if (newItems.Count == oldItems.Count - 1)
{
var differences = oldItems.Except(newItems);
if (differences.Take(2).Count() == 1)
{
for (int i = 0; i < oldItems.Count; i++)
{
if (i >= newItems.Count || !newItems[i].Equals(oldItems[i]))
{
return new(NotifyCollectionChangedAction.Remove, oldItems[i], index: i);
}
}
}
}
// a single item was moved
else if (newItems.Count == oldItems.Count)
{
var differences = oldItems.Except(newItems);
// desync due to skipped/batched calls, reset the list
if (differences.Any())
{
return new(NotifyCollectionChangedAction.Reset);
}

// first diff from reversed is the designated item
for (int i = oldItems.Count - 1; i >= 0; i--)
{
if (!oldItems[i].Equals(newItems[i]))
{
return new(NotifyCollectionChangedAction.Move, oldItems[i], index: 0, oldIndex: i);
}
}
}

return new(NotifyCollectionChangedAction.Reset);
}

public bool CheckIsRecentFilesEnabled()
{
using var subkey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer");
Expand Down Expand Up @@ -246,15 +296,6 @@ public bool CheckIsRecentFilesEnabled()
return true;
}

/// <summary>
/// Returns whether two RecentItem enumerables have the same order.
/// This function depends on `RecentItem` implementing IEquatable.
/// </summary>
private bool RecentItemsOrderEquals(IEnumerable<RecentItem> oldOrder, IEnumerable<RecentItem> newOrder)
{
return oldOrder != null && newOrder != null && oldOrder.SequenceEqual(newOrder);
}

public void Dispose()
{
RecentItemsManager.Default.RecentItemsChanged -= OnRecentItemsChanged;
Expand Down
52 changes: 33 additions & 19 deletions src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
Expand Down Expand Up @@ -138,30 +139,43 @@ private async Task UpdateRecentsList(NotifyCollectionChangedEventArgs e)

switch (e.Action)
{
// currently everything falls under Reset
case NotifyCollectionChangedAction.Add:
if (e.NewItems is not null)
{
var addedItem = e.NewItems.Cast<RecentItem>().Single();
AddItemToRecentList(addedItem, 0);
}
break;

case NotifyCollectionChangedAction.Move:
if (e.OldItems is not null)
{
var movedItem = e.OldItems.Cast<RecentItem>().Single();
recentItemsCollection.RemoveAt(e.OldStartingIndex);
AddItemToRecentList(movedItem, 0);
}
break;

case NotifyCollectionChangedAction.Remove:
if (e.OldItems is not null)
{
var removedItem = e.OldItems.Cast<RecentItem>().Single();
recentItemsCollection.RemoveAt(e.OldStartingIndex);
}
break;

// case NotifyCollectionChangedAction.Reset:
default:
var recentFiles = App.RecentItemsManager.RecentFiles; // already sorted, add all in order
if (!recentFiles.SequenceEqual(recentItemsCollection))
{
var recentFiles = App.RecentItemsManager.RecentFiles; // already sorted, add all in order
int idx = 0;
for (; idx < recentFiles.Count; idx++) // Add new items (top of the list)
{
if (idx >= recentItemsCollection.Count || !recentFiles[idx].Equals(recentItemsCollection[idx]))
{
if (!AddItemToRecentList(recentFiles[idx], idx)) // Not a new item
break;
}
else
break;
}
while (idx < recentItemsCollection.Count) // Remove old items
recentItemsCollection.Clear();
foreach (var item in recentFiles)
{
if (idx >= recentFiles.Count || !recentFiles[idx].Equals(recentItemsCollection[idx]))
recentItemsCollection.RemoveAt(idx);
else
idx++;
AddItemToRecentList(item);
}
break;
}
break;
}

// update chevron if there aren't any items
Expand Down