diff --git a/src/Files.App/Data/Contracts/IGeneralSettingsService.cs b/src/Files.App/Data/Contracts/IGeneralSettingsService.cs
index e473508791fe..8889f13c8113 100644
--- a/src/Files.App/Data/Contracts/IGeneralSettingsService.cs
+++ b/src/Files.App/Data/Contracts/IGeneralSettingsService.cs
@@ -304,5 +304,20 @@ public interface IGeneralSettingsService : IBaseSettingsService, INotifyProperty
/// Gets or sets a value whether the filter header should be displayed.
///
bool ShowFilterHeader { get; set; }
+
+ ///
+ /// Gets or sets the preferred search engine.
+ ///
+ PreferredSearchEngine PreferredSearchEngine { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to use Everything for folder size calculations.
+ ///
+ bool UseEverythingForFolderSizes { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of results Everything should return for folder size calculations.
+ ///
+ int EverythingMaxFolderSizeResults { get; set; }
}
}
diff --git a/src/Files.App/Data/Enums/PreferredSearchEngine.cs b/src/Files.App/Data/Enums/PreferredSearchEngine.cs
new file mode 100644
index 000000000000..a8dd3d665a43
--- /dev/null
+++ b/src/Files.App/Data/Enums/PreferredSearchEngine.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+namespace Files.App.Data.Enums
+{
+ ///
+ /// Defines constants that specify the preferred search engine.
+ ///
+ public enum PreferredSearchEngine
+ {
+ ///
+ /// Windows Search engine.
+ ///
+ Windows,
+
+ ///
+ /// Everything search engine.
+ ///
+ Everything,
+ }
+}
diff --git a/src/Files.App/Files.App.csproj b/src/Files.App/Files.App.csproj
index 6ed8fc5d9166..3b6d8fccfa5a 100644
--- a/src/Files.App/Files.App.csproj
+++ b/src/Files.App/Files.App.csproj
@@ -59,6 +59,15 @@
PreserveNewest
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
diff --git a/src/Files.App/GlobalUsings.cs b/src/Files.App/GlobalUsings.cs
index 438eebbcdbea..f845de8d5c4b 100644
--- a/src/Files.App/GlobalUsings.cs
+++ b/src/Files.App/GlobalUsings.cs
@@ -15,6 +15,9 @@
global using global::System.Text.Json.Serialization;
global using SystemIO = global::System.IO;
+// Microsoft Extensions
+global using global::Microsoft.Extensions.Logging;
+
// CommunityToolkit.Mvvm
global using global::CommunityToolkit.Mvvm.ComponentModel;
global using global::CommunityToolkit.Mvvm.DependencyInjection;
diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs
index 9be8c4bd051f..05985346f250 100644
--- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs
+++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs
@@ -4,6 +4,7 @@
using Files.App.Helpers.Application;
using Files.App.Services.SizeProvider;
using Files.App.Utils.Logger;
+using Files.App.Utils.Storage.Search;
using Files.App.ViewModels.Settings;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -246,6 +247,11 @@ public static IHost ConfigureHost()
.AddSingleton()
.AddSingleton()
.AddSingleton()
+ // Search Engine Services
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
// ViewModels
.AddSingleton()
.AddSingleton()
diff --git a/src/Files.App/Libraries/Everything32.dll b/src/Files.App/Libraries/Everything32.dll
new file mode 100644
index 000000000000..fed6852d8a65
Binary files /dev/null and b/src/Files.App/Libraries/Everything32.dll differ
diff --git a/src/Files.App/Libraries/Everything64.dll b/src/Files.App/Libraries/Everything64.dll
new file mode 100644
index 000000000000..f34745ac8d7f
Binary files /dev/null and b/src/Files.App/Libraries/Everything64.dll differ
diff --git a/src/Files.App/Libraries/EverythingARM64.dll b/src/Files.App/Libraries/EverythingARM64.dll
new file mode 100644
index 000000000000..81dbd4e62bd8
Binary files /dev/null and b/src/Files.App/Libraries/EverythingARM64.dll differ
diff --git a/src/Files.App/Services/Search/EverythingSdk3Service.cs b/src/Files.App/Services/Search/EverythingSdk3Service.cs
new file mode 100644
index 000000000000..247bd057b1ab
--- /dev/null
+++ b/src/Files.App/Services/Search/EverythingSdk3Service.cs
@@ -0,0 +1,317 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Files.App.Services.Search
+{
+ ///
+ /// Everything SDK3 (v1.5) implementation for improved performance
+ ///
+ internal sealed class EverythingSdk3Service : IDisposable
+ {
+ #region SDK3 Definitions
+
+ private const uint EVERYTHING3_OK = 0;
+ private const uint EVERYTHING3_ERROR_OUT_OF_MEMORY = 0xE0000001;
+ private const uint EVERYTHING3_ERROR_IPC_PIPE_NOT_FOUND = 0xE0000002;
+ private const uint EVERYTHING3_ERROR_DISCONNECTED = 0xE0000003;
+ private const uint EVERYTHING3_ERROR_INVALID_PARAMETER = 0xE0000004;
+ private const uint EVERYTHING3_ERROR_BAD_REQUEST = 0xE0000005;
+ private const uint EVERYTHING3_ERROR_CANCELLED = 0xE0000006;
+ private const uint EVERYTHING3_ERROR_PROPERTY_NOT_FOUND = 0xE0000007;
+ private const uint EVERYTHING3_ERROR_SERVER = 0xE0000008;
+ private const uint EVERYTHING3_ERROR_INVALID_COMMAND = 0xE0000009;
+ private const uint EVERYTHING3_ERROR_BAD_RESPONSE = 0xE000000A;
+ private const uint EVERYTHING3_ERROR_INSUFFICIENT_BUFFER = 0xE000000B;
+ private const uint EVERYTHING3_ERROR_SHUTDOWN = 0xE000000C;
+
+ // Property IDs
+ private const uint EVERYTHING3_PROPERTY_SIZE = 0x00000001;
+ private const uint EVERYTHING3_PROPERTY_DATE_MODIFIED = 0x00000002;
+ private const uint EVERYTHING3_PROPERTY_DATE_CREATED = 0x00000003;
+ private const uint EVERYTHING3_PROPERTY_ATTRIBUTES = 0x00000004;
+ private const uint EVERYTHING3_PROPERTY_PATH = 0x00000005;
+ private const uint EVERYTHING3_PROPERTY_NAME = 0x00000006;
+ private const uint EVERYTHING3_PROPERTY_EXTENSION = 0x00000007;
+ private const uint EVERYTHING3_PROPERTY_TYPE_NAME = 0x00000008;
+
+ // Result types
+ private const uint EVERYTHING3_RESULT_TYPE_FILE = 1;
+ private const uint EVERYTHING3_RESULT_TYPE_FOLDER = 2;
+
+ #endregion
+
+ #region P/Invoke Declarations
+
+ [DllImport("Everything3", CharSet = CharSet.Unicode)]
+ private static extern IntPtr Everything3_ConnectW(string instance_name);
+
+ [DllImport("Everything3")]
+ private static extern bool Everything3_DestroyClient(IntPtr client);
+
+ [DllImport("Everything3")]
+ private static extern bool Everything3_ShutdownClient(IntPtr client);
+
+ [DllImport("Everything3", CharSet = CharSet.Unicode)]
+ private static extern ulong Everything3_GetFolderSizeFromFilenameW(IntPtr client, string filename);
+
+ [DllImport("Everything3", CharSet = CharSet.Unicode)]
+ private static extern IntPtr Everything3_CreateQuery(IntPtr client, string search_string);
+
+ [DllImport("Everything3")]
+ private static extern bool Everything3_DestroyQuery(IntPtr query);
+
+ [DllImport("Everything3")]
+ private static extern bool Everything3_SetMax(IntPtr query, uint max);
+
+ [DllImport("Everything3")]
+ private static extern bool Everything3_SetOffset(IntPtr query, uint offset);
+
+ [DllImport("Everything3")]
+ private static extern bool Everything3_SetRequestProperties(IntPtr query, uint property_ids, uint property_count);
+
+ [DllImport("Everything3")]
+ private static extern bool Everything3_Execute(IntPtr query);
+
+ [DllImport("Everything3")]
+ private static extern uint Everything3_GetCount(IntPtr query);
+
+ [DllImport("Everything3")]
+ private static extern uint Everything3_GetResultType(IntPtr query, uint index);
+
+ [DllImport("Everything3", CharSet = CharSet.Unicode)]
+ private static extern IntPtr Everything3_GetResultPathW(IntPtr query, uint index);
+
+ [DllImport("Everything3", CharSet = CharSet.Unicode)]
+ private static extern IntPtr Everything3_GetResultNameW(IntPtr query, uint index);
+
+ [DllImport("Everything3")]
+ private static extern ulong Everything3_GetResultSize(IntPtr query, uint index);
+
+ [DllImport("Everything3")]
+ private static extern ulong Everything3_GetResultDateModified(IntPtr query, uint index);
+
+ [DllImport("Everything3")]
+ private static extern ulong Everything3_GetResultDateCreated(IntPtr query, uint index);
+
+ [DllImport("Everything3")]
+ private static extern uint Everything3_GetResultAttributes(IntPtr query, uint index);
+
+ [DllImport("Everything3")]
+ private static extern uint Everything3_GetLastError();
+
+ #endregion
+
+ private IntPtr _client;
+ private readonly object _lock = new object();
+ private bool _disposed;
+
+ public bool IsConnected => _client != IntPtr.Zero;
+
+ public bool Connect()
+ {
+ lock (_lock)
+ {
+ if (_client != IntPtr.Zero)
+ return true;
+
+ try
+ {
+ // Try to connect to unnamed instance first
+ _client = Everything3_ConnectW(null);
+ if (_client != IntPtr.Zero)
+ {
+ App.Logger?.LogInformation("[Everything SDK3] Connected to unnamed instance");
+ return true;
+ }
+
+ // Try to connect to 1.5a instance
+ _client = Everything3_ConnectW("1.5a");
+ if (_client != IntPtr.Zero)
+ {
+ App.Logger?.LogInformation("[Everything SDK3] Connected to 1.5a instance");
+ return true;
+ }
+
+ App.Logger?.LogWarning("[Everything SDK3] Failed to connect to Everything 1.5");
+ return false;
+ }
+ catch (DllNotFoundException)
+ {
+ App.Logger?.LogInformation("[Everything SDK3] SDK3 DLL not found - Everything 1.5 not installed");
+ return false;
+ }
+ catch (EntryPointNotFoundException)
+ {
+ App.Logger?.LogWarning("[Everything SDK3] SDK3 entry point not found - incompatible DLL");
+ return false;
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, "[Everything SDK3] Error connecting to Everything 1.5");
+ return false;
+ }
+ }
+ }
+
+ public ulong GetFolderSize(string folderPath)
+ {
+ if (!IsConnected || string.IsNullOrEmpty(folderPath))
+ return 0;
+
+ lock (_lock)
+ {
+ try
+ {
+ var size = Everything3_GetFolderSizeFromFilenameW(_client, folderPath);
+
+ // Check for errors (-1 indicates error)
+ if (size == ulong.MaxValue)
+ {
+ var error = Everything3_GetLastError();
+ App.Logger?.LogWarning($"[Everything SDK3] GetFolderSize failed for {folderPath}, error: 0x{error:X8}");
+ return 0;
+ }
+
+ App.Logger?.LogInformation($"[Everything SDK3] Got folder size for {folderPath}: {size} bytes");
+ return size;
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, $"[Everything SDK3] Error getting folder size for {folderPath}");
+ return 0;
+ }
+ }
+ }
+
+ public async Task> SearchAsync(
+ string searchQuery,
+ uint maxResults = 1000,
+ CancellationToken cancellationToken = default)
+ {
+ if (!IsConnected || string.IsNullOrEmpty(searchQuery))
+ return new List<(string, string, bool, ulong, DateTime, DateTime, uint)>();
+
+ return await Task.Run(() =>
+ {
+ lock (_lock)
+ {
+ IntPtr query = IntPtr.Zero;
+ var results = new List<(string Path, string Name, bool IsFolder, ulong Size, DateTime DateModified, DateTime DateCreated, uint Attributes)>();
+
+ try
+ {
+ query = Everything3_CreateQuery(_client, searchQuery);
+ if (query == IntPtr.Zero)
+ {
+ App.Logger?.LogWarning("[Everything SDK3] Failed to create query");
+ return results;
+ }
+
+ // Set max results
+ Everything3_SetMax(query, maxResults);
+
+ // Request properties we need
+ uint[] properties = {
+ EVERYTHING3_PROPERTY_PATH,
+ EVERYTHING3_PROPERTY_NAME,
+ EVERYTHING3_PROPERTY_SIZE,
+ EVERYTHING3_PROPERTY_DATE_MODIFIED,
+ EVERYTHING3_PROPERTY_DATE_CREATED,
+ EVERYTHING3_PROPERTY_ATTRIBUTES
+ };
+
+ GCHandle propertiesHandle = GCHandle.Alloc(properties, GCHandleType.Pinned);
+ try
+ {
+ Everything3_SetRequestProperties(query, (uint)propertiesHandle.AddrOfPinnedObject(), (uint)properties.Length);
+ }
+ finally
+ {
+ propertiesHandle.Free();
+ }
+
+ // Execute query
+ if (!Everything3_Execute(query))
+ {
+ var error = Everything3_GetLastError();
+ App.Logger?.LogWarning($"[Everything SDK3] Query execution failed, error: 0x{error:X8}");
+ return results;
+ }
+
+ var count = Everything3_GetCount(query);
+ App.Logger?.LogInformation($"[Everything SDK3] Query returned {count} results");
+
+ for (uint i = 0; i < count; i++)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ break;
+
+ try
+ {
+ var type = Everything3_GetResultType(query, i);
+ var isFolder = type == EVERYTHING3_RESULT_TYPE_FOLDER;
+
+ var path = Marshal.PtrToStringUni(Everything3_GetResultPathW(query, i)) ?? string.Empty;
+ var name = Marshal.PtrToStringUni(Everything3_GetResultNameW(query, i)) ?? string.Empty;
+ var size = Everything3_GetResultSize(query, i);
+ var dateModified = DateTime.FromFileTimeUtc((long)Everything3_GetResultDateModified(query, i));
+ var dateCreated = DateTime.FromFileTimeUtc((long)Everything3_GetResultDateCreated(query, i));
+ var attributes = Everything3_GetResultAttributes(query, i);
+
+ results.Add((path, name, isFolder, size, dateModified, dateCreated, attributes));
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, $"[Everything SDK3] Error processing result {i}");
+ }
+ }
+
+ return results;
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, "[Everything SDK3] Error during search");
+ return results;
+ }
+ finally
+ {
+ if (query != IntPtr.Zero)
+ Everything3_DestroyQuery(query);
+ }
+ }
+ }, cancellationToken);
+ }
+
+ public void Dispose()
+ {
+ lock (_lock)
+ {
+ if (_disposed)
+ return;
+
+ if (_client != IntPtr.Zero)
+ {
+ try
+ {
+ Everything3_ShutdownClient(_client);
+ Everything3_DestroyClient(_client);
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, "[Everything SDK3] Error during cleanup");
+ }
+ _client = IntPtr.Zero;
+ }
+
+ _disposed = true;
+ }
+ }
+ }
+}
diff --git a/src/Files.App/Services/Search/EverythingSearchService.cs b/src/Files.App/Services/Search/EverythingSearchService.cs
new file mode 100644
index 000000000000..b2e411923cef
--- /dev/null
+++ b/src/Files.App/Services/Search/EverythingSearchService.cs
@@ -0,0 +1,601 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Files.App.Data.Models;
+using Files.App.ViewModels;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.IO;
+using Windows.Storage;
+using Microsoft.Extensions.Logging;
+
+namespace Files.App.Services.Search
+{
+ public sealed class EverythingSearchService : IEverythingSearchService
+ {
+ // Everything API constants
+ private const int EVERYTHING_OK = 0;
+ private const int EVERYTHING_ERROR_IPC = 2;
+
+ private const int EVERYTHING_REQUEST_FILE_NAME = 0x00000001;
+ private const int EVERYTHING_REQUEST_PATH = 0x00000002;
+ private const int EVERYTHING_REQUEST_DATE_MODIFIED = 0x00000040;
+ private const int EVERYTHING_REQUEST_SIZE = 0x00000010;
+ private const int EVERYTHING_REQUEST_DATE_CREATED = 0x00000020;
+ private const int EVERYTHING_REQUEST_ATTRIBUTES = 0x00000100;
+
+ // Architecture-aware DLL name
+ private static readonly string EverythingDllName = GetArchitectureSpecificDllName();
+
+ private static string GetArchitectureSpecificDllName()
+ {
+ // Check for ARM64 first
+ if (System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture == System.Runtime.InteropServices.Architecture.Arm64)
+ {
+ // Use native ARM64 DLL for better performance
+ return "EverythingARM64.dll";
+ }
+
+ // Standard x86/x64 detection
+ return Environment.Is64BitProcess ? "Everything64.dll" : "Everything32.dll";
+ }
+
+ // Everything API imports - using architecture-aware DLL resolution
+ [DllImport("Everything", EntryPoint = "Everything_SetSearchW", CharSet = CharSet.Unicode)]
+ private static extern uint Everything_SetSearchW(string lpSearchString);
+
+ [DllImport("Everything", EntryPoint = "Everything_SetMatchPath")]
+ private static extern void Everything_SetMatchPath(bool bEnable);
+
+ [DllImport("Everything", EntryPoint = "Everything_SetMatchCase")]
+ private static extern void Everything_SetMatchCase(bool bEnable);
+
+ [DllImport("Everything", EntryPoint = "Everything_SetMatchWholeWord")]
+ private static extern void Everything_SetMatchWholeWord(bool bEnable);
+
+ [DllImport("Everything", EntryPoint = "Everything_SetRegex")]
+ private static extern void Everything_SetRegex(bool bEnable);
+
+ [DllImport("Everything", EntryPoint = "Everything_SetMax")]
+ private static extern void Everything_SetMax(uint dwMax);
+
+ [DllImport("Everything", EntryPoint = "Everything_SetOffset")]
+ private static extern void Everything_SetOffset(uint dwOffset);
+
+ [DllImport("Everything", EntryPoint = "Everything_SetRequestFlags")]
+ private static extern void Everything_SetRequestFlags(uint dwRequestFlags);
+
+ [DllImport("Everything", EntryPoint = "Everything_QueryW")]
+ private static extern bool Everything_QueryW(bool bWait);
+
+ [DllImport("Everything", EntryPoint = "Everything_GetNumResults")]
+ private static extern uint Everything_GetNumResults();
+
+ [DllImport("Everything", EntryPoint = "Everything_GetLastError")]
+ private static extern uint Everything_GetLastError();
+
+ [DllImport("Everything", EntryPoint = "Everything_IsFileResult")]
+ private static extern bool Everything_IsFileResult(uint nIndex);
+
+ [DllImport("Everything", EntryPoint = "Everything_IsFolderResult")]
+ private static extern bool Everything_IsFolderResult(uint nIndex);
+
+ [DllImport("Everything", EntryPoint = "Everything_GetResultPath", CharSet = CharSet.Unicode)]
+ private static extern IntPtr Everything_GetResultPath(uint nIndex);
+
+ [DllImport("Everything", EntryPoint = "Everything_GetResultFileName", CharSet = CharSet.Unicode)]
+ private static extern IntPtr Everything_GetResultFileName(uint nIndex);
+
+ [DllImport("Everything", EntryPoint = "Everything_GetResultDateModified")]
+ private static extern bool Everything_GetResultDateModified(uint nIndex, out long lpFileTime);
+
+ [DllImport("Everything", EntryPoint = "Everything_GetResultDateCreated")]
+ private static extern bool Everything_GetResultDateCreated(uint nIndex, out long lpFileTime);
+
+ [DllImport("Everything", EntryPoint = "Everything_GetResultSize")]
+ private static extern bool Everything_GetResultSize(uint nIndex, out long lpFileSize);
+
+ [DllImport("Everything", EntryPoint = "Everything_GetResultAttributes")]
+ private static extern uint Everything_GetResultAttributes(uint nIndex);
+
+ [DllImport("Everything", EntryPoint = "Everything_Reset")]
+ private static extern void Everything_Reset();
+
+ [DllImport("Everything", EntryPoint = "Everything_SetSort")]
+ private static extern void Everything_SetSort(uint dwSortType);
+
+ // Note: Everything_CleanUp is intentionally not imported as it can cause access violations
+ // Everything_Reset() is sufficient for resetting the query state between searches
+
+ [DllImport("Everything", EntryPoint = "Everything_IsDBLoaded")]
+ private static extern bool Everything_IsDBLoaded();
+
+ // Win32 API imports for DLL loading
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern IntPtr LoadLibrary(string lpLibFileName);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern bool FreeLibrary(IntPtr hLibModule);
+
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ private static extern bool SetDllDirectory(string lpPathName);
+
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ private static extern IntPtr AddDllDirectory(string newDirectory);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern bool RemoveDllDirectory(IntPtr cookie);
+
+ private readonly IUserSettingsService _userSettingsService;
+ private static readonly object _dllSetupLock = new object();
+ private static IntPtr _everythingModule = IntPtr.Zero;
+ private static readonly List _dllDirectoryCookies = new();
+ private static bool _dllDirectorySet = false;
+ private static bool _everythingAvailable = false;
+ private static bool _availabilityChecked = false;
+ private static DateTime _lastAvailabilityCheck = default;
+
+ // SDK3 support
+ private static EverythingSdk3Service _sdk3Service;
+ private static bool _sdk3Available = false;
+ private static bool _sdk3Checked = false;
+
+ static EverythingSearchService()
+ {
+ // Set up DLL import resolver for architecture-aware loading
+ NativeLibrary.SetDllImportResolver(typeof(EverythingSearchService).Assembly, DllImportResolver);
+ }
+
+ public EverythingSearchService(IUserSettingsService userSettingsService)
+ {
+ _userSettingsService = userSettingsService;
+
+ // Set up DLL search path if not already done
+ lock (_dllSetupLock)
+ {
+ if (!_dllDirectorySet)
+ {
+ SetupDllSearchPath();
+ _dllDirectorySet = true;
+ }
+ }
+ }
+
+ private static IntPtr DllImportResolver(string libraryName, System.Reflection.Assembly assembly, DllImportSearchPath? searchPath)
+ {
+ if (libraryName == "Everything")
+ {
+ lock (_dllSetupLock)
+ {
+ if (_everythingModule != IntPtr.Zero)
+ {
+ return _everythingModule;
+ }
+
+
+ // Try to load Everything DLL from various locations
+ var appDirectory = AppContext.BaseDirectory;
+ var possiblePaths = new[]
+ {
+ Path.Combine(appDirectory, "Libraries", EverythingDllName),
+ Path.Combine(appDirectory, EverythingDllName),
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Everything", EverythingDllName),
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Everything", EverythingDllName),
+ Path.Combine(@"C:\Program Files\Everything", EverythingDllName),
+ Path.Combine(@"C:\Program Files (x86)\Everything", EverythingDllName),
+ EverythingDllName // Try system path
+ };
+
+ foreach (var path in possiblePaths)
+ {
+ if (File.Exists(path))
+ {
+ try
+ {
+ _everythingModule = LoadLibrary(path);
+ if (_everythingModule != IntPtr.Zero)
+ {
+ return _everythingModule;
+ }
+ else
+ {
+ var error = Marshal.GetLastWin32Error();
+ }
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ else
+ {
+ }
+ }
+
+ // If not found, let the default resolver handle it
+ return IntPtr.Zero;
+ }
+ }
+
+ // Use default resolver for other libraries
+ return NativeLibrary.Load(libraryName, assembly, searchPath);
+ }
+
+ private static void SetupDllSearchPath()
+ {
+ try
+ {
+ // Get the application directory
+ var appDirectory = AppContext.BaseDirectory;
+ var searchPaths = new[]
+ {
+ appDirectory,
+ Path.Combine(appDirectory, "Libraries"),
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Everything"),
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Everything")
+ };
+
+ foreach (var path in searchPaths)
+ {
+ if (Directory.Exists(path))
+ {
+ var cookie = AddDllDirectory(path);
+ if (cookie != IntPtr.Zero)
+ {
+ _dllDirectoryCookies.Add(cookie);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error setting up DLL search paths: {ex.Message}");
+ }
+ }
+
+ public static void CleanupDllDirectories()
+ {
+ lock (_dllSetupLock)
+ {
+ foreach (var cookie in _dllDirectoryCookies)
+ {
+ RemoveDllDirectory(cookie);
+ }
+ _dllDirectoryCookies.Clear();
+
+ if (_everythingModule != IntPtr.Zero)
+ {
+ FreeLibrary(_everythingModule);
+ _everythingModule = IntPtr.Zero;
+ }
+ }
+ }
+
+ public bool IsEverythingAvailable()
+ {
+ lock (_dllSetupLock)
+ {
+ // Re-check availability every 30 seconds to detect if Everything is started/stopped
+ if (_availabilityChecked && _lastAvailabilityCheck != default &&
+ DateTime.UtcNow - _lastAvailabilityCheck < TimeSpan.FromSeconds(30))
+ {
+ return _everythingAvailable || _sdk3Available;
+ }
+
+ // Check SDK3 first (Everything 1.5)
+ // Note: SDK3 DLLs are not included and must be obtained separately from:
+ // https://github.com/voidtools/everything_sdk3
+ if (!_sdk3Checked)
+ {
+ try
+ {
+ if (_sdk3Service == null)
+ _sdk3Service = new EverythingSdk3Service();
+
+ _sdk3Available = _sdk3Service.Connect();
+ _sdk3Checked = true;
+
+ if (_sdk3Available)
+ {
+ App.Logger?.LogInformation("[Everything] Everything SDK3 (v1.5) is available");
+ _lastAvailabilityCheck = DateTime.UtcNow;
+ return true;
+ }
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogWarning(ex, "[Everything] SDK3 not available, falling back to SDK2");
+ _sdk3Available = false;
+ _sdk3Checked = true;
+ }
+ }
+
+ try
+ {
+ // First check if Everything process is running
+ var everythingProcesses = System.Diagnostics.Process.GetProcessesByName("Everything");
+
+ if (everythingProcesses.Length == 0)
+ {
+ App.Logger?.LogInformation("[Everything] Everything process not found - Everything is not running");
+ _everythingAvailable = false;
+ _availabilityChecked = true;
+ _lastAvailabilityCheck = DateTime.UtcNow;
+ return false;
+ }
+
+ // Try to perform a simple query to test if Everything is accessible
+ bool queryExecuted = false;
+ try
+ {
+ Everything_Reset();
+ Everything_SetSearchW("test");
+ Everything_SetMax(1);
+
+ queryExecuted = Everything_QueryW(false);
+ var lastError = Everything_GetLastError();
+
+ _everythingAvailable = queryExecuted && lastError == EVERYTHING_OK;
+ _availabilityChecked = true;
+ _lastAvailabilityCheck = DateTime.UtcNow;
+
+ if (_everythingAvailable)
+ {
+ App.Logger?.LogInformation("[Everything] Everything SDK2 (v1.4) is available and responding");
+ }
+ else
+ {
+ App.Logger?.LogWarning($"[Everything] Everything is not available. Query result: {queryExecuted}, Error: {lastError}");
+ }
+ }
+ finally
+ {
+ // Note: Not calling Everything_CleanUp() to avoid access violations
+ // Everything_Reset() will be called on the next query which handles cleanup
+ }
+
+ return _everythingAvailable;
+ }
+ catch (Exception ex)
+ {
+ _everythingAvailable = false;
+ _availabilityChecked = true;
+ _lastAvailabilityCheck = DateTime.UtcNow;
+ return false;
+ }
+ }
+ }
+
+ public async Task> SearchAsync(string query, string searchPath = null, CancellationToken cancellationToken = default)
+ {
+ if (!IsEverythingAvailable())
+ {
+ return new List();
+ }
+
+ // Try SDK3 first if available
+ if (_sdk3Available && _sdk3Service != null)
+ {
+ try
+ {
+ var searchQuery = BuildOptimizedQuery(query, searchPath);
+ App.Logger?.LogInformation($"[Everything SDK3] Executing search query: '{searchQuery}'");
+
+ var sdk3Results = await _sdk3Service.SearchAsync(searchQuery, 1000, cancellationToken);
+ var results = new List();
+
+ foreach (var (path, name, isFolder, size, dateModified, dateCreated, attributes) in sdk3Results)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ break;
+
+ var fullPath = string.IsNullOrEmpty(path) ? name : Path.Combine(path, name);
+
+ // Skip if it doesn't match our filter criteria
+ if (!string.IsNullOrEmpty(searchPath) && searchPath != "Home" &&
+ !fullPath.StartsWith(searchPath, StringComparison.OrdinalIgnoreCase))
+ continue;
+
+ var isHidden = (attributes & 0x02) != 0; // FILE_ATTRIBUTE_HIDDEN
+
+ // Check user settings for hidden items
+ if (isHidden && !_userSettingsService.FoldersSettingsService.ShowHiddenItems)
+ continue;
+
+ // Check for dot files
+ if (name.StartsWith('.') && !_userSettingsService.FoldersSettingsService.ShowDotFiles)
+ continue;
+
+ var item = new ListedItem(null)
+ {
+ PrimaryItemAttribute = isFolder ? StorageItemTypes.Folder : StorageItemTypes.File,
+ ItemNameRaw = name,
+ ItemPath = fullPath,
+ ItemDateModifiedReal = dateModified,
+ ItemDateCreatedReal = dateCreated,
+ IsHiddenItem = isHidden,
+ LoadFileIcon = false,
+ FileExtension = isFolder ? null : Path.GetExtension(fullPath),
+ FileSizeBytes = isFolder ? 0 : (long)size,
+ FileSize = isFolder ? null : ByteSizeLib.ByteSize.FromBytes(size).ToBinaryString(),
+ Opacity = isHidden ? Constants.UI.DimItemOpacity : 1
+ };
+
+ if (!isFolder)
+ {
+ item.ItemType = item.FileExtension?.Trim('.') + " " + Strings.File.GetLocalizedResource();
+ }
+
+ results.Add(item);
+ }
+
+ App.Logger?.LogInformation($"[Everything SDK3] Search completed with {results.Count} results");
+ return results;
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, "[Everything SDK3] Search failed, falling back to SDK2");
+ // Fall through to SDK2
+ }
+ }
+
+ // SDK2 fallback
+ return await Task.Run(() =>
+ {
+ var results = new List();
+ bool queryExecuted = false;
+
+ try
+ {
+ Everything_Reset();
+
+ // Set up the search query
+ var searchQuery = BuildOptimizedQuery(query, searchPath);
+ Everything_SetSearchW(searchQuery);
+ Everything_SetMatchCase(false);
+ Everything_SetRequestFlags(
+ EVERYTHING_REQUEST_FILE_NAME |
+ EVERYTHING_REQUEST_PATH |
+ EVERYTHING_REQUEST_DATE_MODIFIED |
+ EVERYTHING_REQUEST_DATE_CREATED |
+ EVERYTHING_REQUEST_SIZE |
+ EVERYTHING_REQUEST_ATTRIBUTES);
+
+ // Limit results to prevent overwhelming the UI
+ Everything_SetMax(1000);
+
+ // Execute the query
+ App.Logger?.LogInformation($"[Everything SDK2] Executing search query: '{searchQuery}'");
+ queryExecuted = Everything_QueryW(true);
+ if (!queryExecuted)
+ {
+ var error = Everything_GetLastError();
+ if (error == EVERYTHING_ERROR_IPC)
+ {
+ return results;
+ }
+ else
+ {
+ return results;
+ }
+ }
+
+ var numResults = Everything_GetNumResults();
+ App.Logger?.LogInformation($"[Everything SDK2] Query returned {numResults} results");
+
+ for (uint i = 0; i < numResults; i++)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ break;
+
+ try
+ {
+ var fileName = Marshal.PtrToStringUni(Everything_GetResultFileName(i));
+ var path = Marshal.PtrToStringUni(Everything_GetResultPath(i));
+
+ if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(path))
+ continue;
+
+ var fullPath = Path.Combine(path, fileName);
+
+ // Skip if it doesn't match our filter criteria
+ if (!string.IsNullOrEmpty(searchPath) && searchPath != "Home" &&
+ !fullPath.StartsWith(searchPath, StringComparison.OrdinalIgnoreCase))
+ continue;
+
+ var isFolder = Everything_IsFolderResult(i);
+ var attributes = Everything_GetResultAttributes(i);
+ var isHidden = (attributes & 0x02) != 0; // FILE_ATTRIBUTE_HIDDEN
+
+ // Check user settings for hidden items
+ if (isHidden && !_userSettingsService.FoldersSettingsService.ShowHiddenItems)
+ continue;
+
+ // Check for dot files
+ if (fileName.StartsWith('.') && !_userSettingsService.FoldersSettingsService.ShowDotFiles)
+ continue;
+
+ Everything_GetResultDateModified(i, out long dateModified);
+ Everything_GetResultDateCreated(i, out long dateCreated);
+ Everything_GetResultSize(i, out long size);
+
+ var item = new ListedItem(null)
+ {
+ PrimaryItemAttribute = isFolder ? StorageItemTypes.Folder : StorageItemTypes.File,
+ ItemNameRaw = fileName,
+ ItemPath = fullPath,
+ ItemDateModifiedReal = DateTime.FromFileTime(dateModified),
+ ItemDateCreatedReal = DateTime.FromFileTime(dateCreated),
+ IsHiddenItem = isHidden,
+ LoadFileIcon = false,
+ FileExtension = isFolder ? null : Path.GetExtension(fullPath),
+ FileSizeBytes = isFolder ? 0 : size,
+ FileSize = isFolder ? null : ByteSizeLib.ByteSize.FromBytes((ulong)size).ToBinaryString(),
+ Opacity = isHidden ? Constants.UI.DimItemOpacity : 1
+ };
+
+ if (!isFolder)
+ {
+ item.ItemType = item.FileExtension?.Trim('.') + " " + Strings.File.GetLocalizedResource();
+ }
+
+ results.Add(item);
+ }
+ catch (Exception ex)
+ {
+ // Skip items that cause errors
+ System.Diagnostics.Debug.WriteLine($"Error processing Everything result {i}: {ex.Message}");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, "[Everything SDK2] Search error");
+ }
+ finally
+ {
+ // Note: We're not calling Everything_CleanUp() here as it can cause access violations
+ // The Everything API manages its own memory and calling CleanUp can interfere with
+ // the API's internal state, especially when multiple queries are executed in sequence
+ // Everything_Reset() at the start of each query is sufficient for cleanup
+ }
+
+ return results;
+ }, cancellationToken);
+ }
+
+ private string BuildOptimizedQuery(string query, string searchPath)
+ {
+ if (string.IsNullOrEmpty(searchPath) || searchPath == "Home")
+ {
+ return query;
+ }
+ else if (searchPath.Length <= 3) // Root drive like C:\
+ {
+ return $"path:\"{searchPath}\" {query}";
+ }
+ else
+ {
+ var escapedPath = searchPath.Replace("\"", "\"\"");
+ return $"path:\"{escapedPath}\" {query}";
+ }
+ }
+
+ public async Task> FilterItemsAsync(IEnumerable items, string query, CancellationToken cancellationToken = default)
+ {
+ // For filtering existing items, we'll use Everything's search on the current directory
+ var firstItem = items.FirstOrDefault();
+ if (firstItem == null)
+ return new List();
+
+ // Get the directory path from the first item
+ var directoryPath = Path.GetDirectoryName(firstItem.ItemPath);
+
+ // Search within this directory
+ var searchResults = await SearchAsync(query, directoryPath, cancellationToken);
+
+ // Return only items that exist in the original collection
+ var itemPaths = new HashSet(items.Select(i => i.ItemPath), StringComparer.OrdinalIgnoreCase);
+ return searchResults.Where(r => itemPaths.Contains(r.ItemPath)).ToList();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Files.App/Services/Search/Everything_SDK3_README.md b/src/Files.App/Services/Search/Everything_SDK3_README.md
new file mode 100644
index 000000000000..2c2bd10ad268
--- /dev/null
+++ b/src/Files.App/Services/Search/Everything_SDK3_README.md
@@ -0,0 +1,41 @@
+# Everything SDK3 (v1.5) Implementation
+
+## Overview
+This implementation adds support for Everything SDK3 (v1.5) with automatic fallback to SDK2 (v1.4).
+
+## Features
+- **SDK3 Support**: Uses Everything 1.5's new SDK3 for improved performance
+- **Direct Folder Size Query**: SDK3's `Everything3_GetFolderSizeFromFilenameW()` provides instant folder sizes
+- **Automatic Fallback**: Falls back to SDK2 if Everything 1.5 is not installed
+- **Architecture Support**: Works with x86, x64, and ARM64
+
+## Implementation Status
+✅ SDK3 service implementation (`EverythingSdk3Service.cs`)
+✅ Integration with main Everything service
+✅ Folder size calculation optimization
+✅ Search functionality with SDK3
+✅ Graceful fallback handling
+
+## Requirements
+- Everything 1.5 alpha or later (for SDK3 features)
+- SDK3 DLLs (not included - must be obtained separately)
+
+## DLL Requirements
+The SDK3 implementation requires the following DLLs:
+- `Everything3.dll` (x86)
+- `Everything3-x64.dll` (x64)
+- `Everything3-arm64.dll` (ARM64)
+
+These DLLs are not included in the Files repository and must be obtained from:
+https://github.com/voidtools/everything_sdk3
+
+## Usage
+The implementation automatically detects and uses SDK3 when available:
+1. On startup, it attempts to connect to Everything 1.5
+2. If successful, SDK3 features are used for improved performance
+3. If not available, it falls back to SDK2 (Everything 1.4)
+
+## Performance Benefits
+- **Folder Size Calculation**: Near-instant with SDK3 vs enumeration with SDK2
+- **Search Performance**: Improved query handling in SDK3
+- **Memory Usage**: More efficient memory management in SDK3
\ No newline at end of file
diff --git a/src/Files.App/Services/Search/IEverythingSearchService.cs b/src/Files.App/Services/Search/IEverythingSearchService.cs
new file mode 100644
index 000000000000..9834230d423f
--- /dev/null
+++ b/src/Files.App/Services/Search/IEverythingSearchService.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+namespace Files.App.Services.Search
+{
+ public interface IEverythingSearchService
+ {
+ bool IsEverythingAvailable();
+ Task> SearchAsync(string query, string searchPath = null, CancellationToken cancellationToken = default);
+ Task> FilterItemsAsync(IEnumerable items, string query, CancellationToken cancellationToken = default);
+ }
+}
\ No newline at end of file
diff --git a/src/Files.App/Services/Settings/GeneralSettingsService.cs b/src/Files.App/Services/Settings/GeneralSettingsService.cs
index addc268ce18a..768f02cc50ad 100644
--- a/src/Files.App/Services/Settings/GeneralSettingsService.cs
+++ b/src/Files.App/Services/Settings/GeneralSettingsService.cs
@@ -369,13 +369,31 @@ public bool ShowShelfPane
set => Set(value);
}
- public bool ShowFilterHeader
- {
- get => Get(false);
- set => Set(value);
- }
+ public bool ShowFilterHeader
+ {
+ get => Get(false);
+ set => Set(value);
+ }
+
+ public PreferredSearchEngine PreferredSearchEngine
+ {
+ get => (PreferredSearchEngine)Get((long)PreferredSearchEngine.Windows);
+ set => Set((long)value);
+ }
+
+ public bool UseEverythingForFolderSizes
+ {
+ get => Get(false);
+ set => Set(value);
+ }
+
+ public int EverythingMaxFolderSizeResults
+ {
+ get => Get(1000);
+ set => Set(value);
+ }
- protected override void RaiseOnSettingChangedEvent(object sender, SettingChangedEventArgs e)
+ protected override void RaiseOnSettingChangedEvent(object sender, SettingChangedEventArgs e)
{
base.RaiseOnSettingChangedEvent(sender, e);
}
diff --git a/src/Files.App/Services/SizeProvider/EverythingSizeProvider.cs b/src/Files.App/Services/SizeProvider/EverythingSizeProvider.cs
new file mode 100644
index 000000000000..a521348f399e
--- /dev/null
+++ b/src/Files.App/Services/SizeProvider/EverythingSizeProvider.cs
@@ -0,0 +1,357 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Files.App.Services.Search;
+using System;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Files.App.Services.SizeProvider
+{
+ public sealed class EverythingSizeProvider : ISizeProvider
+ {
+ private readonly ConcurrentDictionary sizes = new();
+ private readonly IEverythingSearchService everythingService;
+ private readonly IGeneralSettingsService generalSettings;
+ private static EverythingSdk3Service _sdk3Service;
+ private readonly SemaphoreSlim _calculationSemaphore = new(3); // Limit concurrent calculations
+
+ public event EventHandler? SizeChanged;
+
+ // Everything API imports for folder size calculation
+ [DllImport("Everything", EntryPoint = "Everything_SetSearchW", CharSet = CharSet.Unicode)]
+ private static extern uint Everything_SetSearchW(string lpSearchString);
+
+ [DllImport("Everything", EntryPoint = "Everything_SetRequestFlags")]
+ private static extern void Everything_SetRequestFlags(uint dwRequestFlags);
+
+ [DllImport("Everything", EntryPoint = "Everything_SetMax")]
+ private static extern void Everything_SetMax(uint dwMax);
+
+ [DllImport("Everything", EntryPoint = "Everything_QueryW")]
+ private static extern bool Everything_QueryW(bool bWait);
+
+ [DllImport("Everything", EntryPoint = "Everything_GetNumResults")]
+ private static extern uint Everything_GetNumResults();
+
+ [DllImport("Everything", EntryPoint = "Everything_GetResultSize")]
+ private static extern bool Everything_GetResultSize(uint nIndex, out long lpFileSize);
+
+ [DllImport("Everything", EntryPoint = "Everything_Reset")]
+ private static extern void Everything_Reset();
+
+ [DllImport("Everything", EntryPoint = "Everything_SetSort")]
+ private static extern void Everything_SetSort(uint dwSortType);
+
+ [DllImport("Everything", EntryPoint = "Everything_IsFileResult")]
+ private static extern bool Everything_IsFileResult(uint nIndex);
+
+ [DllImport("Everything", EntryPoint = "Everything_GetResultPath", CharSet = CharSet.Unicode)]
+ private static extern IntPtr Everything_GetResultPath(uint nIndex);
+
+ [DllImport("Everything", EntryPoint = "Everything_GetResultFileName", CharSet = CharSet.Unicode)]
+ private static extern IntPtr Everything_GetResultFileName(uint nIndex);
+
+ // Note: Everything_CleanUp is intentionally not imported as it can cause access violations
+ // Everything_Reset() is sufficient for resetting the query state between searches
+
+ private const int EVERYTHING_REQUEST_SIZE = 0x00000010;
+
+ public EverythingSizeProvider(IEverythingSearchService everythingSearchService, IGeneralSettingsService generalSettingsService)
+ {
+ everythingService = everythingSearchService;
+ generalSettings = generalSettingsService;
+ }
+
+ public Task CleanAsync() => Task.CompletedTask;
+
+ public Task ClearAsync()
+ {
+ sizes.Clear();
+ return Task.CompletedTask;
+ }
+
+ public async Task UpdateAsync(string path, CancellationToken cancellationToken)
+ {
+ // Return cached size immediately if available
+ if (sizes.TryGetValue(path, out ulong cachedSize))
+ {
+ RaiseSizeChanged(path, cachedSize, SizeChangedValueState.Final);
+ return;
+ }
+
+ // Indicate that calculation is starting
+ RaiseSizeChanged(path, 0, SizeChangedValueState.None);
+
+ // Run the entire calculation on a background thread to avoid blocking
+ await Task.Run(async () =>
+ {
+ // Limit concurrent calculations
+ await _calculationSemaphore.WaitAsync(cancellationToken);
+ try
+ {
+ // Check if Everything is available
+ if (!everythingService.IsEverythingAvailable())
+ {
+ await FallbackCalculateAsync(path, cancellationToken);
+ return;
+ }
+
+ try
+ {
+ // Calculate using Everything
+ ulong totalSize = await CalculateWithEverythingAsync(path, cancellationToken);
+
+ if (totalSize == 0)
+ {
+ // Everything returned 0, use fallback
+ await FallbackCalculateAsync(path, cancellationToken);
+ return;
+ }
+ sizes[path] = totalSize;
+ RaiseSizeChanged(path, totalSize, SizeChangedValueState.Final);
+ }
+ catch (Exception ex)
+ {
+ // Fall back to standard calculation on error
+ await FallbackCalculateAsync(path, cancellationToken);
+ }
+ }
+ finally
+ {
+ _calculationSemaphore.Release();
+ }
+ }, cancellationToken).ConfigureAwait(false);
+ }
+
+ private async Task CalculateWithEverythingAsync(string path, CancellationToken cancellationToken)
+ {
+ // Try SDK3 first if available
+ if (_sdk3Service == null)
+ {
+ try
+ {
+ _sdk3Service = new EverythingSdk3Service();
+ if (_sdk3Service.Connect())
+ {
+ App.Logger?.LogInformation("[EverythingSizeProvider] Connected to Everything SDK3 for folder size calculation");
+ }
+ else
+ {
+ _sdk3Service?.Dispose();
+ _sdk3Service = null;
+ }
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogWarning(ex, "[EverythingSizeProvider] SDK3 not available");
+ _sdk3Service = null;
+ }
+ }
+
+ if (_sdk3Service != null && _sdk3Service.IsConnected)
+ {
+ try
+ {
+ // Use a timeout for SDK3 queries to prevent hanging
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ cts.CancelAfter(TimeSpan.FromSeconds(2)); // 2 second timeout
+
+ var size = await Task.Run(() => _sdk3Service.GetFolderSize(path), cts.Token);
+ if (size > 0)
+ {
+ App.Logger?.LogInformation($"[EverythingSizeProvider SDK3] Got folder size for {path}: {size} bytes");
+ return size;
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ App.Logger?.LogWarning($"[EverythingSizeProvider SDK3] Timeout getting folder size for {path}");
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, $"[EverythingSizeProvider SDK3] Error getting folder size for {path}");
+ }
+ }
+
+ // Fall back to SDK2
+ return await Task.Run(() =>
+ {
+ bool queryExecuted = false;
+ try
+ {
+ // IMPORTANT: For large directories like C:\, this query can return millions of results
+ // causing Everything to run out of memory. For root drives, fall back to standard calculation.
+ if (path.Length <= 3 && path.EndsWith(":\\"))
+ {
+ return 0UL; // Will trigger fallback calculation
+ }
+
+ // For large known directories, also skip Everything
+ var knownLargePaths = new[] {
+ @"C:\Windows",
+ @"C:\Program Files",
+ @"C:\Program Files (x86)",
+ @"C:\Users",
+ @"C:\ProgramData",
+ @"C:\$Recycle.Bin",
+ Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+ Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)
+ };
+
+ if (knownLargePaths.Any(largePath =>
+ string.Equals(path, largePath, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(Path.GetFullPath(path), Path.GetFullPath(largePath), StringComparison.OrdinalIgnoreCase)))
+ {
+ return 0UL; // Will trigger fallback calculation
+ }
+
+ // Reset Everything state
+ Everything_Reset();
+
+ // Use an optimized query that only returns files (not folders) to reduce result count
+ // The folder: syntax searches recursively within the specified folder
+ // Adding file: ensures we only get files, not directories
+ var searchQuery = $"folder:\"{path}\" file:";
+ Everything_SetSearchW(searchQuery);
+
+ // Request only size information to optimize performance
+ Everything_SetRequestFlags(EVERYTHING_REQUEST_SIZE);
+
+ // Sort by size descending to prioritize large files if we hit the limit
+ Everything_SetSort(13); // EVERYTHING_SORT_SIZE_DESCENDING
+
+ // Use configurable max results limit
+ var maxResults = (uint)generalSettings.EverythingMaxFolderSizeResults;
+ Everything_SetMax(maxResults);
+
+ queryExecuted = Everything_QueryW(true);
+ if (!queryExecuted)
+ return 0UL;
+
+ var numResults = Everything_GetNumResults();
+
+ // If we hit the limit, we're still getting the largest files first
+ // This gives a more accurate estimate even with limited results
+ if (numResults >= maxResults)
+ {
+ App.Logger?.LogInformation($"[EverythingSizeProvider SDK2] Hit result limit ({maxResults}) for {path}, results may be incomplete");
+ }
+
+ ulong totalSize = 0;
+ int validResults = 0;
+
+ for (uint i = 0; i < numResults; i++)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ break;
+
+ if (Everything_GetResultSize(i, out long size) && size > 0)
+ {
+ totalSize += (ulong)size;
+ validResults++;
+ }
+ }
+
+ // If we got very few results or hit the limit for a folder that should have more files,
+ // fall back to standard calculation
+ if (numResults >= maxResults && validResults < 100)
+ {
+ App.Logger?.LogInformation($"[EverythingSizeProvider SDK2] Too few valid results ({validResults}) for {path}, using fallback");
+ return 0UL; // Will trigger fallback calculation
+ }
+
+ App.Logger?.LogInformation($"[EverythingSizeProvider SDK2] Calculated {totalSize} bytes for {path} ({validResults} files)");
+ return totalSize;
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, $"[EverythingSizeProvider SDK2] Error calculating with Everything for {path}");
+ return 0UL;
+ }
+ finally
+ {
+ // Note: We're not calling Everything_CleanUp() here as it can cause access violations
+ // The Everything API manages its own memory and calling CleanUp can interfere with
+ // the API's internal state, especially when multiple queries are executed in sequence
+ // Everything_Reset() at the start of each query is sufficient for cleanup
+ }
+ }, cancellationToken);
+ }
+
+ private async Task FallbackCalculateAsync(string path, CancellationToken cancellationToken)
+ {
+ // Fallback to directory enumeration if Everything is not available
+ ulong size = await CalculateRecursive(path, cancellationToken);
+ sizes[path] = size;
+ RaiseSizeChanged(path, size, SizeChangedValueState.Final);
+
+ async Task CalculateRecursive(string currentPath, CancellationToken ct, int level = 0)
+ {
+ if (string.IsNullOrEmpty(currentPath))
+ return 0;
+
+ ulong totalSize = 0;
+
+ try
+ {
+ var directory = new DirectoryInfo(currentPath);
+
+ // Get files in current directory
+ foreach (var file in directory.GetFiles())
+ {
+ if (ct.IsCancellationRequested)
+ break;
+
+ totalSize += (ulong)file.Length;
+ }
+
+ // Recursively process subdirectories
+ foreach (var subDirectory in directory.GetDirectories())
+ {
+ if (ct.IsCancellationRequested)
+ break;
+
+ // Skip symbolic links and junctions
+ if ((subDirectory.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
+ continue;
+
+ var subDirSize = await CalculateRecursive(subDirectory.FullName, ct, level + 1);
+ totalSize += subDirSize;
+ }
+
+ // Update intermediate results for top-level calculation
+ // Note: Removed stopwatch tracking for simplicity after logging removal
+ }
+ catch (UnauthorizedAccessException)
+ {
+ // Skip directories we can't access
+ }
+ catch (DirectoryNotFoundException)
+ {
+ // Directory was deleted during enumeration
+ }
+
+ return totalSize;
+ }
+ }
+
+ public bool TryGetSize(string path, out ulong size) => sizes.TryGetValue(path, out size);
+
+ public void Dispose()
+ {
+ _calculationSemaphore?.Dispose();
+ _sdk3Service?.Dispose();
+ }
+
+ private void RaiseSizeChanged(string path, ulong newSize, SizeChangedValueState valueState)
+ => SizeChanged?.Invoke(this, new SizeChangedEventArgs(path, newSize, valueState));
+ }
+}
\ No newline at end of file
diff --git a/src/Files.App/Services/SizeProvider/UserSizeProvider.cs b/src/Files.App/Services/SizeProvider/UserSizeProvider.cs
index a86413955440..fb5525aa2c8f 100644
--- a/src/Files.App/Services/SizeProvider/UserSizeProvider.cs
+++ b/src/Files.App/Services/SizeProvider/UserSizeProvider.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using Files.App.Services.SizeProvider;
+using Files.App.Services.Search;
namespace Files.App.Services
{
@@ -9,6 +10,10 @@ public sealed partial class UserSizeProvider : ISizeProvider
{
private readonly IFoldersSettingsService folderPreferences
= Ioc.Default.GetRequiredService();
+ private readonly IGeneralSettingsService generalSettings
+ = Ioc.Default.GetRequiredService();
+ private readonly IEverythingSearchService everythingSearchService
+ = Ioc.Default.GetRequiredService();
private ISizeProvider provider;
@@ -20,6 +25,7 @@ public UserSizeProvider()
provider.SizeChanged += Provider_SizeChanged;
folderPreferences.PropertyChanged += FolderPreferences_PropertyChanged;
+ generalSettings.PropertyChanged += GeneralSettings_PropertyChanged;
}
public Task CleanAsync()
@@ -38,10 +44,29 @@ public void Dispose()
{
provider.Dispose();
folderPreferences.PropertyChanged -= FolderPreferences_PropertyChanged;
+ generalSettings.PropertyChanged -= GeneralSettings_PropertyChanged;
}
private ISizeProvider GetProvider()
- => folderPreferences.CalculateFolderSizes ? new DrivesSizeProvider() : new NoSizeProvider();
+ {
+ if (!folderPreferences.CalculateFolderSizes)
+ return new NoSizeProvider();
+
+ // Use Everything for folder sizes if it's selected and available
+ if (generalSettings.PreferredSearchEngine == Data.Enums.PreferredSearchEngine.Everything)
+ {
+ if (everythingSearchService.IsEverythingAvailable())
+ {
+ return new EverythingSizeProvider(everythingSearchService, generalSettings);
+ }
+ else
+ {
+ }
+ }
+
+ // Fall back to standard provider
+ return new DrivesSizeProvider();
+ }
private async void FolderPreferences_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
@@ -54,6 +79,22 @@ private async void FolderPreferences_PropertyChanged(object sender, PropertyChan
}
}
+ private async void GeneralSettings_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName is nameof(IGeneralSettingsService.PreferredSearchEngine) ||
+ e.PropertyName is nameof(IGeneralSettingsService.EverythingMaxFolderSizeResults))
+ {
+ // Only update if folder size calculation is enabled
+ if (folderPreferences.CalculateFolderSizes)
+ {
+ await provider.ClearAsync();
+ provider.SizeChanged -= Provider_SizeChanged;
+ provider = GetProvider();
+ provider.SizeChanged += Provider_SizeChanged;
+ }
+ }
+ }
+
private void Provider_SizeChanged(object sender, SizeChangedEventArgs e)
=> SizeChanged?.Invoke(this, e);
}
diff --git a/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs b/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs
index bd86be9c4d3a..71852a955ecd 100644
--- a/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs
+++ b/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs
@@ -78,8 +78,18 @@ Func, Task> intermediateAction
folder.FileSizeBytes = (long)size;
folder.FileSize = size.ToSizeString();
}
-
- _ = folderSizeProvider.UpdateAsync(folder.ItemPath, cancellationToken);
+ else
+ {
+ // Fire and forget - calculate size in background without blocking
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await folderSizeProvider.UpdateAsync(folder.ItemPath, cancellationToken);
+ }
+ catch { /* Ignore errors in background size calculation */ }
+ });
+ }
}
}
}
diff --git a/src/Files.App/Utils/Storage/Search/EverythingSearchEngineService.cs b/src/Files.App/Utils/Storage/Search/EverythingSearchEngineService.cs
new file mode 100644
index 000000000000..4a74fae70ed1
--- /dev/null
+++ b/src/Files.App/Utils/Storage/Search/EverythingSearchEngineService.cs
@@ -0,0 +1,136 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Files.App.Utils;
+using Files.App.Helpers.Application;
+using Files.App.Services.Search;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Storage;
+using Microsoft.Extensions.Logging;
+using FileAttributes = System.IO.FileAttributes;
+using WIN32_FIND_DATA = Files.App.Helpers.Win32PInvoke.WIN32_FIND_DATA;
+
+namespace Files.App.Utils.Storage.Search
+{
+ public sealed class EverythingSearchEngineService : ISearchEngineService
+ {
+ private readonly IUserSettingsService UserSettingsService = Ioc.Default.GetRequiredService();
+ private readonly IEverythingSearchService _everythingService = Ioc.Default.GetRequiredService();
+ private const int MaxSuggestionResults = 10;
+ private const int MaxSearchResults = 1000;
+
+ // State for fallback notification (show only once per session)
+ private static bool _hasNotifiedEverythingUnavailable = false;
+ private readonly object _notificationLock = new object();
+
+ public string Name => "Everything";
+
+ public bool IsAvailable => _everythingService.IsEverythingAvailable();
+
+ public async Task> SearchAsync(string query, string? path, CancellationToken ct)
+ {
+ App.Logger?.LogInformation("[SearchEngine: Everything] Starting search - Query: '{Query}', Path: '{Path}'", query, path ?? "");
+
+ if (!IsAvailable)
+ {
+ App.Logger?.LogWarning("[SearchEngine: Everything] Everything search unavailable, performing fallback to Windows Search");
+ await NotifyEverythingUnavailableOnce();
+ return await FallbackToWindowsSearch(query, path, MaxSearchResults, ct);
+ }
+
+ try
+ {
+ var results = await _everythingService.SearchAsync(query, path, ct);
+ App.Logger?.LogInformation("[SearchEngine: Everything] Search completed - Found {ResultCount} results", results.Count);
+ return results;
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, "[SearchEngine: Everything] Search failed, falling back to Windows Search");
+ return await FallbackToWindowsSearch(query, path, MaxSearchResults, ct);
+ }
+ }
+
+ public async Task> SuggestAsync(string query, string? path, CancellationToken ct)
+ {
+ App.Logger?.LogInformation("[SearchEngine: Everything] Starting suggestions - Query: '{Query}', Path: '{Path}'", query, path ?? "");
+
+ if (!IsAvailable)
+ {
+ App.Logger?.LogWarning("[SearchEngine: Everything] Everything search unavailable, performing fallback to Windows Search for suggestions");
+ await NotifyEverythingUnavailableOnce();
+ return await FallbackToWindowsSearch(query, path, MaxSuggestionResults, ct);
+ }
+
+ try
+ {
+ // Use Everything API with limited results for suggestions
+ var results = await _everythingService.SearchAsync(query, path, ct);
+ // Limit to suggestion count
+ var limitedResults = results.Take(MaxSuggestionResults).ToList();
+ App.Logger?.LogInformation("[SearchEngine: Everything] Suggestions completed - Found {ResultCount} results", limitedResults.Count);
+ return limitedResults;
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, "[SearchEngine: Everything] Suggestions failed, falling back to Windows Search");
+ return await FallbackToWindowsSearch(query, path, MaxSuggestionResults, ct);
+ }
+ }
+
+ ///
+ /// Notifies user once per session that Everything is unavailable and Windows Search fallback is being used
+ ///
+ private async Task NotifyEverythingUnavailableOnce()
+ {
+ lock (_notificationLock)
+ {
+ if (_hasNotifiedEverythingUnavailable)
+ return;
+
+ _hasNotifiedEverythingUnavailable = true;
+ }
+
+ try
+ {
+ App.Logger?.LogInformation("[SearchEngine: Everything] Showing fallback notification to user");
+
+ // Everything is not available - search will use Windows Search instead
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogWarning(ex, "[SearchEngine: Everything] Failed to show fallback notification");
+ }
+ }
+
+ ///
+ /// Fallback to Windows Search when Everything is unavailable
+ ///
+ private async Task> FallbackToWindowsSearch(string query, string? path, int maxResults, CancellationToken ct)
+ {
+ try
+ {
+ App.Logger?.LogInformation("[SearchEngine: Everything] Falling back to Windows Search");
+
+ // Use Windows Search service as fallback
+ var windowsSearchService = Ioc.Default.GetRequiredService();
+ var results = maxResults == MaxSuggestionResults
+ ? await windowsSearchService.SuggestAsync(query, path, ct)
+ : await windowsSearchService.SearchAsync(query, path, ct);
+
+ App.Logger?.LogInformation("[SearchEngine: Everything] Windows Search fallback completed - Found {ResultCount} results", results.Count);
+ return results;
+ }
+ catch (Exception ex)
+ {
+ App.Logger?.LogError(ex, "[SearchEngine: Everything] Windows Search fallback failed");
+ return new List();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Files.App/Utils/Storage/Search/FolderSearch.cs b/src/Files.App/Utils/Storage/Search/FolderSearch.cs
index 29042ca0f959..a076a0732455 100644
--- a/src/Files.App/Utils/Storage/Search/FolderSearch.cs
+++ b/src/Files.App/Utils/Storage/Search/FolderSearch.cs
@@ -67,29 +67,74 @@ public string AQSQuery
}
}
- public Task SearchAsync(IList results, CancellationToken token)
+ public async Task SearchAsync(IList results, CancellationToken token)
{
try
{
+ // Check if we should use Everything for global search
+ var searchEngine = UserSettingsService.GeneralSettingsService.PreferredSearchEngine;
+
+ if (searchEngine == Files.App.Data.Enums.PreferredSearchEngine.Everything)
+ {
+ var everythingService = Ioc.Default.GetService();
+ if (everythingService != null && everythingService.IsEverythingAvailable())
+ {
+ try
+ {
+ var everythingResults = await everythingService.SearchAsync(Query, Folder, token);
+
+ if (everythingResults != null && everythingResults.Count > 0)
+ {
+ // Fix: UsedMaxItemCount can be uint.MaxValue which overflows when cast to int
+ var itemsToTake = UsedMaxItemCount == uint.MaxValue ? everythingResults.Count : Math.Min(everythingResults.Count, (int)UsedMaxItemCount);
+
+ foreach (var item in everythingResults.Take(itemsToTake))
+ {
+ if (item == null)
+ continue;
+ if (token.IsCancellationRequested)
+ break;
+
+ results.Add(item);
+
+ if (results.Count == 32 || results.Count % 300 == 0)
+ {
+ SearchTick?.Invoke(this, EventArgs.Empty);
+ }
+ }
+ SearchTick?.Invoke(this, EventArgs.Empty);
+ return;
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ return;
+ }
+ catch (Exception ex)
+ {
+ // Fall through to use default search
+ }
+ }
+ }
+
+ // Fall back to Windows Search
if (App.LibraryManager.TryGetLibrary(Folder, out var library))
{
- return AddItemsForLibraryAsync(library, results, token);
+ await AddItemsForLibraryAsync(library, results, token);
}
else if (Folder == "Home")
{
- return AddItemsForHomeAsync(results, token);
+ await AddItemsForHomeAsync(results, token);
}
else
{
- return AddItemsAsync(Folder, results, token);
+ await AddItemsAsync(Folder, results, token);
}
}
catch (Exception e)
{
- App.Logger.LogWarning(e, "Search failure");
+ App.Logger?.LogWarning(e, "Search failure");
}
-
- return Task.CompletedTask;
}
private async Task AddItemsForHomeAsync(IList results, CancellationToken token)
@@ -128,7 +173,7 @@ public async Task> SearchAsync()
}
catch (Exception e)
{
- App.Logger.LogWarning(e, "Search failure");
+ App.Logger?.LogWarning(e, "Search failure");
}
return results;
@@ -160,7 +205,7 @@ private async Task SearchAsync(BaseStorageFolder folder, IList resul
}
catch (Exception ex)
{
- App.Logger.LogWarning(ex, "Error creating ListedItem from StorageItem");
+ App.Logger?.LogWarning(ex, "Error creating ListedItem from StorageItem");
}
if (results.Count == 32 || results.Count % 300 == 0 /*|| sampler.CheckNow()*/)
@@ -242,7 +287,7 @@ private async Task SearchTagsAsync(string folder, IList results, Can
}
catch (Exception ex)
{
- App.Logger.LogWarning(ex, "Error creating ListedItem from StorageItem");
+ App.Logger?.LogWarning(ex, "Error creating ListedItem from StorageItem");
}
}
diff --git a/src/Files.App/Utils/Storage/Search/ISearchEngineSelector.cs b/src/Files.App/Utils/Storage/Search/ISearchEngineSelector.cs
new file mode 100644
index 000000000000..7face2020f83
--- /dev/null
+++ b/src/Files.App/Utils/Storage/Search/ISearchEngineSelector.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+namespace Files.App.Utils.Storage.Search
+{
+ ///
+ /// Service for selecting the appropriate search engine based on user settings
+ ///
+ public interface ISearchEngineSelector
+ {
+ ///
+ /// Gets the currently selected search engine service
+ ///
+ ISearchEngineService Current { get; }
+
+ ///
+ /// Gets the currently selected search engine service
+ ///
+ /// The active search engine service
+ ISearchEngineService GetCurrentSearchEngine();
+
+ ///
+ /// Gets the search engine service by name
+ ///
+ /// The name of the search engine
+ /// The requested search engine service, or null if not found
+ ISearchEngineService? GetSearchEngineByName(string name);
+
+ ///
+ /// Gets all available search engine services
+ ///
+ /// Collection of all search engine services
+ IEnumerable GetAllSearchEngines();
+ }
+}
diff --git a/src/Files.App/Utils/Storage/Search/ISearchEngineService.cs b/src/Files.App/Utils/Storage/Search/ISearchEngineService.cs
new file mode 100644
index 000000000000..88035e0ec8dd
--- /dev/null
+++ b/src/Files.App/Utils/Storage/Search/ISearchEngineService.cs
@@ -0,0 +1,15 @@
+using Files.App.Utils;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Files.App.Utils.Storage.Search
+{
+ public interface ISearchEngineService
+ {
+ Task> SearchAsync(string query, string? path, CancellationToken ct);
+ Task> SuggestAsync(string query, string? path, CancellationToken ct);
+ string Name { get; } // "Windows Search", "Everything"
+ bool IsAvailable { get; }
+ }
+}
diff --git a/src/Files.App/Utils/Storage/Search/SearchEngineSelector.cs b/src/Files.App/Utils/Storage/Search/SearchEngineSelector.cs
new file mode 100644
index 000000000000..4eb156bac7b3
--- /dev/null
+++ b/src/Files.App/Utils/Storage/Search/SearchEngineSelector.cs
@@ -0,0 +1,112 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Files.App.Data.Contracts;
+using Files.App.Data.Enums;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Files.App.Utils.Storage.Search
+{
+ ///
+ /// Service for selecting the appropriate search engine based on user settings and availability
+ ///
+ public sealed class SearchEngineSelector : ISearchEngineSelector
+ {
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IUserSettingsService _userSettingsService;
+ private readonly List _searchEngines;
+
+ public SearchEngineSelector(IServiceProvider serviceProvider, IUserSettingsService userSettingsService)
+ {
+ _serviceProvider = serviceProvider;
+ _userSettingsService = userSettingsService;
+
+ // Get all search engine service instances
+ _searchEngines = new List
+ {
+ _serviceProvider.GetRequiredService(),
+ _serviceProvider.GetRequiredService()
+ };
+ }
+
+ ///
+ public ISearchEngineService Current => GetCurrentSearchEngine();
+
+ ///
+ public ISearchEngineService GetCurrentSearchEngine()
+ {
+ try
+ {
+ App.Logger.LogDebug("[SearchEngineSelector] Determining current search engine");
+
+ // Get user's preferred search engine from settings
+ // For now, we'll use a simple property check (this would need to be added to user settings)
+ // In a real implementation, you'd have a setting like:
+ // var preferredEngine = _userSettingsService.GeneralSettingsService.PreferredSearchEngine;
+
+ // Fallback logic: Try to get preferred engine by name, fallback to available engines
+ var preferredEngineName = GetPreferredSearchEngineName();
+ App.Logger.LogDebug("[SearchEngineSelector] Preferred engine: '{PreferredEngine}'", preferredEngineName);
+
+ // First, try to get the preferred engine if it's available
+ var preferredEngine = _searchEngines.FirstOrDefault(engine =>
+ engine.Name.Equals(preferredEngineName, StringComparison.OrdinalIgnoreCase) && engine.IsAvailable);
+
+ if (preferredEngine != null)
+ {
+ App.Logger.LogInformation("[SearchEngineSelector] Using preferred search engine: '{EngineName}'", preferredEngine.Name);
+ return preferredEngine;
+ }
+
+ App.Logger.LogWarning("[SearchEngineSelector] Preferred engine '{PreferredEngine}' not available, falling back", preferredEngineName);
+
+ // Fallback to first available engine (Windows Search should always be available)
+ var fallbackEngine = _searchEngines.FirstOrDefault(engine => engine.IsAvailable);
+
+ if (fallbackEngine != null)
+ {
+ App.Logger.LogInformation("[SearchEngineSelector] Using fallback search engine: '{EngineName}'", fallbackEngine.Name);
+ return fallbackEngine;
+ }
+
+ // If no engines are available, return Windows Search as final fallback
+ var finalFallback = _searchEngines.First(engine => engine is WindowsSearchEngineService);
+ App.Logger.LogWarning("[SearchEngineSelector] No engines available, using final fallback: '{EngineName}'", finalFallback.Name);
+ return finalFallback;
+ }
+ catch (Exception ex)
+ {
+ App.Logger.LogError(ex, "[SearchEngineSelector] Error determining current search engine, falling back to Windows Search");
+ return _searchEngines.First(engine => engine is WindowsSearchEngineService);
+ }
+ }
+
+ ///
+ public ISearchEngineService? GetSearchEngineByName(string name)
+ {
+ return _searchEngines.FirstOrDefault(engine =>
+ engine.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
+ }
+
+ ///
+ public IEnumerable GetAllSearchEngines()
+ {
+ return _searchEngines.AsReadOnly();
+ }
+
+ ///
+ /// Gets the preferred search engine name from user settings
+ ///
+ private string GetPreferredSearchEngineName()
+ {
+ var preferredEngine = _userSettingsService.GeneralSettingsService.PreferredSearchEngine;
+ return preferredEngine switch
+ {
+ PreferredSearchEngine.Everything => "Everything",
+ PreferredSearchEngine.Windows => "Windows Search",
+ _ => "Windows Search"
+ };
+ }
+ }
+}
diff --git a/src/Files.App/Utils/Storage/Search/WindowsSearchEngineService.cs b/src/Files.App/Utils/Storage/Search/WindowsSearchEngineService.cs
new file mode 100644
index 000000000000..769c6bbfb7a2
--- /dev/null
+++ b/src/Files.App/Utils/Storage/Search/WindowsSearchEngineService.cs
@@ -0,0 +1,90 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Files.App.Utils;
+using Files.App.Utils.Storage;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Files.App.Utils.Storage.Search
+{
+ public sealed class WindowsSearchEngineService : ISearchEngineService
+ {
+ public string Name => "Windows Search";
+
+ public bool IsAvailable => true; // Windows Search is generally available
+
+ ///
+ /// Builds an optimized query string for Windows Search with optional path scoping
+ ///
+ /// The search query
+ /// Optional path to scope the search to. Pass null for global search.
+ /// The query (Windows Search handles path scoping internally via FolderSearch)
+ public string BuildOptimizedQuery(string query, string? searchPath)
+ {
+ // For Windows Search, FolderSearch handles path scoping internally
+ // so we just return the original query
+ return query ?? string.Empty;
+ }
+
+ public async Task> SearchAsync(string query, string? path, CancellationToken ct)
+ {
+ App.Logger.LogInformation("[SearchEngine: Windows Search] Starting search - Query: '{Query}', Path: '{Path}'", query, path ?? "");
+
+ // Handle the path scoping logic to match EverythingSearchEngineService behavior
+ var searchPath = GetSearchPath(path);
+ App.Logger.LogDebug("[SearchEngine: Windows Search] Resolved search path: '{SearchPath}'", searchPath ?? "");
+
+ var folderSearch = new FolderSearch
+ {
+ Query = query,
+ Folder = searchPath
+ };
+
+ var results = new List();
+ await folderSearch.SearchAsync(results, ct);
+
+ App.Logger.LogInformation("[SearchEngine: Windows Search] Search completed - Found {ResultCount} results", results.Count);
+ return results;
+ }
+
+ public async Task> SuggestAsync(string query, string? path, CancellationToken ct)
+ {
+ App.Logger.LogInformation("[SearchEngine: Windows Search] Starting suggestions - Query: '{Query}', Path: '{Path}'", query, path ?? "");
+
+ // Handle the path scoping logic to match EverythingSearchEngineService behavior
+ var searchPath = GetSearchPath(path);
+ App.Logger.LogDebug("[SearchEngine: Windows Search] Resolved search path for suggestions: '{SearchPath}'", searchPath ?? "");
+
+ var folderSearch = new FolderSearch
+ {
+ Query = query,
+ Folder = searchPath,
+ MaxItemCount = 10 // Limit suggestions to reasonable number
+ };
+
+ var results = new List();
+ await folderSearch.SearchAsync(results, ct);
+
+ App.Logger.LogInformation("[SearchEngine: Windows Search] Suggestions completed - Found {ResultCount} results", results.Count);
+ return results;
+ }
+
+ ///
+ /// Gets the appropriate search path, handling global search by passing null
+ ///
+ /// The requested search path
+ /// The path to use for FolderSearch, or null for global search
+ private string? GetSearchPath(string? path)
+ {
+ // If path is null or empty, return null for global search
+ if (string.IsNullOrEmpty(path))
+ return null;
+
+ // Return the path as-is - FolderSearch will handle Home, library paths, etc.
+ return path;
+ }
+ }
+}
diff --git a/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs b/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs
index 2d8344bffaf2..371e22c2c013 100644
--- a/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs
+++ b/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs
@@ -11,6 +11,8 @@
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Win32.Storage.FileSystem;
+using Files.App.Utils.Storage.Search;
+using System.Runtime.CompilerServices;
namespace Files.App.ViewModels.Settings
{
@@ -27,6 +29,9 @@ public sealed partial class AdvancedViewModel : ObservableObject
public ICommand ExportSettingsCommand { get; }
public ICommand ImportSettingsCommand { get; }
public AsyncRelayCommand OpenFilesOnWindowsStartupCommand { get; }
+ public ICommand OpenEverythingDownloadCommand { get; }
+
+ public Dictionary SearchEngineTypes { get; private set; } = [];
public AdvancedViewModel()
@@ -39,6 +44,12 @@ public AdvancedViewModel()
ExportSettingsCommand = new AsyncRelayCommand(ExportSettingsAsync);
ImportSettingsCommand = new AsyncRelayCommand(ImportSettingsAsync);
OpenFilesOnWindowsStartupCommand = new AsyncRelayCommand(OpenFilesOnWindowsStartupAsync);
+ OpenEverythingDownloadCommand = new RelayCommand(OpenEverythingDownload);
+
+ // Initialize search engine types
+ SearchEngineTypes.Add(PreferredSearchEngine.Windows, "Windows Search");
+ SearchEngineTypes.Add(PreferredSearchEngine.Everything, "Everything");
+ SelectedSearchEngineType = SearchEngineTypes[UserSettingsService.GeneralSettingsService.PreferredSearchEngine];
_ = DetectOpenFilesAtStartupAsync();
}
@@ -354,6 +365,71 @@ public bool ShowFlattenOptions
OnPropertyChanged();
}
}
+
+ public PreferredSearchEngine PreferredSearchEngine
+ {
+ get => UserSettingsService.GeneralSettingsService.PreferredSearchEngine;
+ set
+ {
+ if (value == UserSettingsService.GeneralSettingsService.PreferredSearchEngine)
+ return;
+
+ UserSettingsService.GeneralSettingsService.PreferredSearchEngine = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(IsEverythingSearchSelected));
+ }
+ }
+
+ public bool IsEverythingSearchAvailable
+ {
+ get => new EverythingSearchEngineService().IsAvailable;
+ }
+
+ public bool IsEverythingSearchSelected
+ {
+ get => PreferredSearchEngine == PreferredSearchEngine.Everything;
+ }
+
+
+ public int EverythingMaxFolderSizeResults
+ {
+ get => UserSettingsService.GeneralSettingsService.EverythingMaxFolderSizeResults;
+ set
+ {
+ if (value != UserSettingsService.GeneralSettingsService.EverythingMaxFolderSizeResults)
+ {
+ UserSettingsService.GeneralSettingsService.EverythingMaxFolderSizeResults = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ private string selectedSearchEngineType;
+ public string SelectedSearchEngineType
+ {
+ get => selectedSearchEngineType;
+ set
+ {
+ // Check if user is trying to select Everything but it's not available
+ if (value == "Everything" && !IsEverythingSearchAvailable)
+ {
+ // Don't change the selection, show warning
+ ShowEverythingNotInstalledWarning = true;
+ // Force the UI to refresh back to current value
+ OnPropertyChanged(nameof(SelectedSearchEngineType));
+ return;
+ }
+
+ // Hide warning if shown
+ ShowEverythingNotInstalledWarning = false;
+
+ if (SetProperty(ref selectedSearchEngineType, value))
+ {
+ UserSettingsService.GeneralSettingsService.PreferredSearchEngine = SearchEngineTypes.First(e => e.Value == value).Key;
+ OnPropertyChanged(nameof(IsEverythingSearchSelected));
+ }
+ }
+ }
public async Task OpenFilesOnWindowsStartupAsync()
{
var stateMode = await ReadState();
@@ -412,5 +488,34 @@ public async Task ReadState()
var state = await StartupTask.GetAsync("3AA55462-A5FA-4933-88C4-712D0B6CDEBB");
return state.State;
}
+
+ private void OpenEverythingDownload()
+ {
+ var url = "https://www.voidtools.com/";
+ Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
+ }
+
+ private bool showEverythingNotInstalledWarning;
+ public bool ShowEverythingNotInstalledWarning
+ {
+ get => showEverythingNotInstalledWarning;
+ set => SetProperty(ref showEverythingNotInstalledWarning, value);
+ }
+
+ public bool ShowEverythingFolderSizeInfo
+ {
+ get => IsEverythingSearchAvailable && SelectedSearchEngineType == "Everything" && UserSettingsService.FoldersSettingsService.CalculateFolderSizes;
+ }
+
+ public bool CanSelectSearchEngine(string searchEngine)
+ {
+ if (searchEngine == "Everything")
+ {
+ return IsEverythingSearchAvailable;
+ }
+ return true;
+ }
+
+
}
}
diff --git a/src/Files.App/ViewModels/Settings/FoldersViewModel.cs b/src/Files.App/ViewModels/Settings/FoldersViewModel.cs
index bf73b83a098b..9fa278fa2d18 100644
--- a/src/Files.App/ViewModels/Settings/FoldersViewModel.cs
+++ b/src/Files.App/ViewModels/Settings/FoldersViewModel.cs
@@ -18,6 +18,19 @@ public FoldersViewModel()
SizeUnitsOptions.Add(SizeUnitTypes.BinaryUnits, Strings.Binary.GetLocalizedResource());
SizeUnitsOptions.Add(SizeUnitTypes.DecimalUnits, Strings.Decimal.GetLocalizedResource());
SizeUnitFormat = SizeUnitsOptions[UserSettingsService.FoldersSettingsService.SizeUnitFormat];
+
+ // Listen for search engine changes to update folder size info
+ UserSettingsService.GeneralSettingsService.PropertyChanged += GeneralSettingsService_PropertyChanged;
+ }
+
+ private void GeneralSettingsService_PropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(IGeneralSettingsService.PreferredSearchEngine))
+ {
+ OnPropertyChanged(nameof(IsEverythingEnabled));
+ OnPropertyChanged(nameof(FolderSizeWarningMessage));
+ OnPropertyChanged(nameof(FolderSizeInfoMessage));
+ }
}
// Properties
@@ -146,7 +159,38 @@ public bool CalculateFolderSizes
UserSettingsService.FoldersSettingsService.CalculateFolderSizes = value;
OnPropertyChanged();
+ OnPropertyChanged(nameof(FolderSizeWarningMessage));
+ OnPropertyChanged(nameof(FolderSizeInfoMessage));
+ }
+ }
+ }
+
+ public bool IsEverythingEnabled
+ {
+ get => UserSettingsService.GeneralSettingsService.PreferredSearchEngine == Data.Enums.PreferredSearchEngine.Everything;
+ }
+
+ public string FolderSizeWarningMessage
+ {
+ get
+ {
+ if (IsEverythingEnabled)
+ {
+ return ""; // No warning when Everything is enabled
+ }
+ return Strings.ShowFolderSizesWarning.GetLocalizedResource();
+ }
+ }
+
+ public string FolderSizeInfoMessage
+ {
+ get
+ {
+ if (IsEverythingEnabled)
+ {
+ return "Everything search is enabled. Folder sizes will be calculated using Everything's fast indexing.";
}
+ return "";
}
}
diff --git a/src/Files.App/Views/Settings/AdvancedPage.xaml b/src/Files.App/Views/Settings/AdvancedPage.xaml
index 4a001c502182..48efdb102bc1 100644
--- a/src/Files.App/Views/Settings/AdvancedPage.xaml
+++ b/src/Files.App/Views/Settings/AdvancedPage.xaml
@@ -10,6 +10,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Files.App.ViewModels.Settings"
xmlns:wctcontrols="using:CommunityToolkit.WinUI.Controls"
+ xmlns:uc="using:Files.App.UserControls"
mc:Ignorable="d">
@@ -168,6 +169,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Files.App/Views/Settings/FoldersPage.xaml b/src/Files.App/Views/Settings/FoldersPage.xaml
index 83033b9c4305..7362168e9d11 100644
--- a/src/Files.App/Views/Settings/FoldersPage.xaml
+++ b/src/Files.App/Views/Settings/FoldersPage.xaml
@@ -9,8 +9,14 @@
xmlns:uc="using:Files.App.UserControls"
xmlns:vm="using:Files.App.ViewModels.Settings"
xmlns:wctcontrols="using:CommunityToolkit.WinUI.Controls"
+ xmlns:wctconverters="using:CommunityToolkit.WinUI.Converters"
mc:Ignorable="d">
+
+
+
+
+
@@ -185,13 +191,27 @@
-
+
+
+
+
+
+
+