Skip to content

Commit 019685f

Browse files
bagusnlneon-nyan
andcommitted
FileCleanup Improvements #633
# Main Goal Improve FileCleanup logics for items > 1000 as before it froze the UI completely 1. Use SelectAllSafe and UnselectAll to do as described > Improved speed when selecting all items by (from ~140000 ms to ~170ms) when selecting 25000 files. 2. Use .NET's built-in SIMD calculation for summing all the asset sizes 3. Use batching when injecting files to the ListViewTable source ## PR Status : - Overall Status : Done - Commits : Done - Synced to base (Collapse:main) : Yes - Build status : OK - Crashing : No - Bug found caused by PR : 0 Co-authored-by: Kemal Setya Adhi <dev.kemalsetyaa@gmail.com>
2 parents 4c0f1d7 + afc9dd8 commit 019685f

File tree

11 files changed

+517
-191
lines changed

11 files changed

+517
-191
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Collections.Specialized;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
8+
namespace CollapseLauncher.Extension
9+
{
10+
/// <summary>
11+
/// Provides extension methods for <see cref="ObservableCollection{T}"/>.
12+
/// </summary>
13+
/// <typeparam name="T">The type of elements in the collection.</typeparam>
14+
internal static class ObservableCollectionExtension<T>
15+
{
16+
/// <summary>
17+
/// Gets the backing list of the specified collection.
18+
/// </summary>
19+
/// <param name="source">The collection to get the backing list from.</param>
20+
/// <returns>A reference to the backing list of the collection.</returns>
21+
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "items")]
22+
internal static extern ref IList<T> GetBackedCollectionList(Collection<T> source);
23+
24+
/// <summary>
25+
/// Invokes the OnCountPropertyChanged method on the specified observable collection.
26+
/// </summary>
27+
/// <param name="source">The observable collection to invoke the method on.</param>
28+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnCountPropertyChanged")]
29+
internal static extern void OnCountPropertyChanged(ObservableCollection<T> source);
30+
31+
/// <summary>
32+
/// Invokes the OnIndexerPropertyChanged method on the specified observable collection.
33+
/// </summary>
34+
/// <param name="source">The observable collection to invoke the method on.</param>
35+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnIndexerPropertyChanged")]
36+
internal static extern void OnIndexerPropertyChanged(ObservableCollection<T> source);
37+
38+
/// <summary>
39+
/// Invokes the OnCollectionChanged method on the specified observable collection.
40+
/// </summary>
41+
/// <param name="source">The observable collection to invoke the method on.</param>
42+
/// <param name="e">The event arguments for the collection changed event.</param>
43+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnCollectionChanged")]
44+
internal static extern void OnCollectionChanged(ObservableCollection<T> source, NotifyCollectionChangedEventArgs e);
45+
46+
/// <summary>
47+
/// Refreshes all events for the specified observable collection.
48+
/// </summary>
49+
/// <param name="source">The observable collection to invoke the method on.</param>
50+
internal static void RefreshAllEvents(ObservableCollection<T> source)
51+
{
52+
OnCountPropertyChanged(source);
53+
OnIndexerPropertyChanged(source);
54+
OnCollectionChanged(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
55+
}
56+
57+
/// <summary>
58+
/// Removes a range of items from the specified observable collection quickly.
59+
/// </summary>
60+
/// <param name="sourceRange">The list of items to remove from the target collection.</param>
61+
/// <param name="target">The observable collection from which the items will be removed.</param>
62+
/// <exception cref="InvalidCastException">Thrown when the backing list of the target collection cannot be cast to a List{T}.</exception>
63+
/// <remarks>
64+
/// This method directly manipulates the backing list of the observable collection to remove the specified items,
65+
/// and then fires the necessary property changed and collection changed events to update any bindings.
66+
/// </remarks>
67+
internal static void RemoveItemsFast(List<T> sourceRange, ObservableCollection<T> target)
68+
{
69+
// Get the backed list instance of the collection
70+
List<T> targetBackedList = GetBackedCollectionList(target) as List<T> ?? throw new InvalidCastException();
71+
72+
// Get the count and iterate the reference of the T from the source range
73+
ReadOnlySpan<T> sourceRangeSpan = CollectionsMarshal.AsSpan(sourceRange);
74+
int len = sourceRangeSpan.Length - 1;
75+
for (; len >= 0; len--)
76+
{
77+
// Remove the reference of the item T from the target backed list
78+
_ = targetBackedList.Remove(sourceRangeSpan[len]);
79+
}
80+
81+
// Fire the changes event
82+
RefreshAllEvents(target);
83+
}
84+
}
85+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Microsoft.UI.Input;
2+
using Microsoft.UI.Xaml;
3+
using System.Collections.Generic;
4+
using System.Collections.ObjectModel;
5+
using System.Collections.Specialized;
6+
using System.Runtime.CompilerServices;
7+
8+
#nullable enable
9+
namespace CollapseLauncher.Extension
10+
{
11+
internal static partial class UIElementExtensions
12+
{
13+
/// <summary>
14+
/// Set the cursor for the element.
15+
/// </summary>
16+
/// <param name="element">The <seealso cref="UIElement"/> member of an element</param>
17+
/// <param name="inputCursor">The cursor you want to set. Use <see cref="InputSystemCursor.Create"/> to choose the cursor you want to set.</param>
18+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_ProtectedCursor")]
19+
internal static extern void SetCursor(this UIElement element, InputCursor inputCursor);
20+
21+
/// <summary>
22+
/// Set the cursor for the element.
23+
/// </summary>
24+
/// <param name="element">The <seealso cref="UIElement"/> member of an element</param>
25+
/// <param name="inputCursor">The cursor you want to set. Use <see cref="InputSystemCursor.Create"/> to choose the cursor you want to set.</param>
26+
internal static ref T WithCursor<T>(this T element, InputCursor inputCursor) where T : UIElement
27+
{
28+
element.SetCursor(inputCursor);
29+
return ref Unsafe.AsRef(ref element);
30+
}
31+
}
32+
}

CollapseLauncher/Classes/Extension/UIElementExtensions.cs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using Windows.UI;
1919
using Windows.UI.Text;
2020
using Hi3Helper.SentryHelper;
21+
using System.Collections.ObjectModel;
2122

2223
namespace CollapseLauncher.Extension
2324
{
@@ -28,27 +29,8 @@ internal class NavigationViewItemLocaleTextProperty
2829
public string LocalePropertyName { get; set; }
2930
}
3031

31-
internal static class UIElementExtensions
32+
internal static partial class UIElementExtensions
3233
{
33-
/// <summary>
34-
/// Set the cursor for the element.
35-
/// </summary>
36-
/// <param name="element">The <seealso cref="UIElement"/> member of an element</param>
37-
/// <param name="inputCursor">The cursor you want to set. Use <see cref="InputSystemCursor.Create"/> to choose the cursor you want to set.</param>
38-
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_ProtectedCursor")]
39-
internal static extern void SetCursor(this UIElement element, InputCursor inputCursor);
40-
41-
/// <summary>
42-
/// Set the cursor for the element.
43-
/// </summary>
44-
/// <param name="element">The <seealso cref="UIElement"/> member of an element</param>
45-
/// <param name="inputCursor">The cursor you want to set. Use <see cref="InputSystemCursor.Create"/> to choose the cursor you want to set.</param>
46-
internal static ref T WithCursor<T>(this T element, InputCursor inputCursor) where T : UIElement
47-
{
48-
element.SetCursor(inputCursor);
49-
return ref Unsafe.AsRef(ref element);
50-
}
51-
5234
#nullable enable
5335
/// <summary>
5436
/// Set the initial navigation view item's locale binding before getting set with <seealso cref="ApplyNavigationViewItemLocaleTextBindings"/>

CollapseLauncher/Classes/Helper/Loading/LoadingMessageHelper.cs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using CollapseLauncher.Extension;
22
using CollapseLauncher.Helper.Animation;
3+
using CommunityToolkit.WinUI;
34
using CommunityToolkit.WinUI.Animations;
45
using Microsoft.UI.Text;
56
using Microsoft.UI.Xaml;
@@ -64,43 +65,65 @@ internal static void SetProgressBarState(double maxValue = 100d, bool isProgress
6465
/// Show the loading frame.
6566
/// </summary>
6667
internal static async void ShowLoadingFrame()
68+
{
69+
if (currentMainWindow.LoadingStatusBackgroundGrid.DispatcherQueue.HasThreadAccess)
70+
{
71+
await ShowLoadingFrameInner();
72+
return;
73+
}
74+
75+
await currentMainWindow.LoadingStatusBackgroundGrid.DispatcherQueue.EnqueueAsync(ShowLoadingFrameInner);
76+
}
77+
78+
private static async Task ShowLoadingFrameInner()
6779
{
6880
if (isCurrentlyShow) return;
6981

70-
isCurrentlyShow = true;
71-
currentMainWindow!.LoadingStatusGrid!.Visibility = Visibility.Visible;
82+
isCurrentlyShow = true;
83+
currentMainWindow!.LoadingStatusGrid!.Visibility = Visibility.Visible;
7284
currentMainWindow!.LoadingStatusBackgroundGrid!.Visibility = Visibility.Visible;
7385

7486
TimeSpan duration = TimeSpan.FromSeconds(0.25);
7587

7688
await Task.WhenAll(
77-
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusBackgroundGrid, duration,
78-
currentMainWindow.LoadingStatusBackgroundGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 1, 0)),
79-
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusGrid, duration,
80-
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateVector3KeyFrameAnimation("Translation", new Vector3(0,0,currentMainWindow.LoadingStatusGrid.Translation.Z), new Vector3(0, (float)(currentMainWindow.LoadingStatusGrid.ActualHeight + 16), currentMainWindow.LoadingStatusGrid.Translation.Z)),
81-
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 1, 0))
82-
);
89+
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusBackgroundGrid, duration,
90+
currentMainWindow.LoadingStatusBackgroundGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 1, 0)),
91+
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusGrid, duration,
92+
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateVector3KeyFrameAnimation("Translation", new Vector3(0, 0, currentMainWindow.LoadingStatusGrid.Translation.Z), new Vector3(0, (float)(currentMainWindow.LoadingStatusGrid.ActualHeight + 16), currentMainWindow.LoadingStatusGrid.Translation.Z)),
93+
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 1, 0))
94+
);
8395
}
8496

8597
/// <summary>
8698
/// Hide the loading frame (also hide the action button).
8799
/// </summary>
88100
internal static async void HideLoadingFrame()
101+
{
102+
if (currentMainWindow.LoadingStatusBackgroundGrid.DispatcherQueue.HasThreadAccess)
103+
{
104+
await HideLoadingFrameInner();
105+
return;
106+
}
107+
108+
await currentMainWindow.LoadingStatusBackgroundGrid.DispatcherQueue.EnqueueAsync(HideLoadingFrameInner);
109+
}
110+
111+
private static async Task HideLoadingFrameInner()
89112
{
90113
if (!isCurrentlyShow) return;
91114

92115
isCurrentlyShow = false;
93116

94117
TimeSpan duration = TimeSpan.FromSeconds(0.25);
95118
await Task.WhenAll(
96-
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusBackgroundGrid, duration,
97-
currentMainWindow.LoadingStatusBackgroundGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 0, 1)),
98-
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusGrid, duration,
99-
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateVector3KeyFrameAnimation("Translation", new Vector3(0, (float)(currentMainWindow.LoadingStatusGrid.ActualHeight + 16), currentMainWindow.LoadingStatusGrid.Translation.Z), new Vector3(0,0,currentMainWindow.LoadingStatusGrid.Translation.Z)),
100-
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 0, 1))
101-
);
102-
103-
currentMainWindow.LoadingStatusGrid.Visibility = Visibility.Collapsed;
119+
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusBackgroundGrid, duration,
120+
currentMainWindow.LoadingStatusBackgroundGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 0, 1)),
121+
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusGrid, duration,
122+
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateVector3KeyFrameAnimation("Translation", new Vector3(0, (float)(currentMainWindow.LoadingStatusGrid.ActualHeight + 16), currentMainWindow.LoadingStatusGrid.Translation.Z), new Vector3(0, 0, currentMainWindow.LoadingStatusGrid.Translation.Z)),
123+
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 0, 1))
124+
);
125+
126+
currentMainWindow.LoadingStatusGrid.Visibility = Visibility.Collapsed;
104127
currentMainWindow.LoadingStatusBackgroundGrid!.Visibility = Visibility.Collapsed;
105128
HideActionButton();
106129
}

CollapseLauncher/Classes/Helper/StreamUtility.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,14 @@ private static void EnsureFilePathExist([NotNull] string? path)
5050
*/
5151

5252
internal static FileInfo EnsureNoReadOnly(this FileInfo fileInfo)
53+
=> fileInfo.EnsureNoReadOnly(out _);
54+
55+
internal static FileInfo EnsureNoReadOnly(this FileInfo fileInfo, out bool isFileExist)
5356
{
54-
if (!fileInfo.Exists)
57+
if (!(isFileExist = fileInfo.Exists))
5558
return fileInfo;
5659

5760
fileInfo.IsReadOnly = false;
58-
fileInfo.Refresh();
5961

6062
return fileInfo;
6163
}

0 commit comments

Comments
 (0)