Flow.Launcher/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs
Jack251970 49f89e33b5 Make locking operations cancelable with tokens
Updated `_semaphore.WaitAsync` in `EverythingAPI.cs` to accept a `CancellationToken` and handle `OperationCanceledException` gracefully, returning `false` instead of propagating the exception.

Refactored locking mechanisms in `Main.cs` to use `CancellationToken` for `_win32sLock` and `_uwpsLock`. Added `try-catch` blocks to handle `OperationCanceledException` and ensure proper lock release. Methods now return `emptyResults` when operations are canceled.
2025-11-06 20:46:29 +08:00

207 lines
7.7 KiB
C#

using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
{
public static class EverythingApi
{
private const int BufferSize = 4096;
private static readonly SemaphoreSlim _semaphore = new(1, 1);
// cached buffer to remove redundant allocations.
private static readonly StringBuilder buffer = new(BufferSize);
public enum StateCode
{
OK,
MemoryError,
IPCError,
RegisterClassExError,
CreateWindowError,
CreateThreadError,
InvalidIndexError,
InvalidCallError
}
const uint EVERYTHING_REQUEST_FULL_PATH_AND_FILE_NAME = 0x00000004u;
const uint EVERYTHING_REQUEST_RUN_COUNT = 0x00000400u;
/// <summary>
/// Checks whether the sort option is Fast Sort.
/// </summary>
public static bool IsFastSortOption(EverythingSortOption sortOption)
{
var fastSortOptionEnabled = EverythingApiDllImport.Everything_IsFastSort(sortOption);
// If the Everything service is not running, then this call will incorrectly report
// the state as false. This checks for errors thrown by the api and up to the caller to handle.
CheckAndThrowExceptionOnError();
return fastSortOptionEnabled;
}
public static async ValueTask<bool> IsEverythingRunningAsync(CancellationToken token = default)
{
try
{
await _semaphore.WaitAsync(token);
}
catch (OperationCanceledException)
{
return false;
}
try
{
_ = EverythingApiDllImport.Everything_GetMajorVersion();
var result = EverythingApiDllImport.Everything_GetLastError() != StateCode.IPCError;
return result;
}
finally
{
_semaphore.Release();
}
}
/// <summary>
/// Searches the specified key word and reset the everything API afterwards
/// </summary>
/// <param name="option">Search Criteria</param>
/// <param name="token">when cancelled the current search will stop and exit (and would not reset)</param>
/// <returns>An IAsyncEnumerable that will enumerate all results searched by the specific query and option</returns>
public static async IAsyncEnumerable<SearchResult> SearchAsync(EverythingSearchOption option,
[EnumeratorCancellation] CancellationToken token)
{
if (option.Offset < 0)
throw new ArgumentOutOfRangeException(nameof(option.Offset), option.Offset, "Offset must be greater than or equal to 0");
if (option.MaxCount < 0)
throw new ArgumentOutOfRangeException(nameof(option.MaxCount), option.MaxCount, "MaxCount must be greater than or equal to 0");
await _semaphore.WaitAsync();
try
{
if (token.IsCancellationRequested)
yield break;
if (option.Keyword.StartsWith("@"))
{
EverythingApiDllImport.Everything_SetRegex(true);
option.Keyword = option.Keyword[1..];
}
var builder = new StringBuilder();
builder.Append(option.Keyword);
if (!string.IsNullOrWhiteSpace(option.ParentPath))
{
builder.Append($" {(option.IsRecursive ? "" : "parent:")}\"{option.ParentPath}\"");
}
if (option.IsContentSearch)
{
builder.Append($" content:\"{option.ContentSearchKeyword}\"");
}
EverythingApiDllImport.Everything_SetSearchW(builder.ToString());
EverythingApiDllImport.Everything_SetOffset(option.Offset);
EverythingApiDllImport.Everything_SetMax(option.MaxCount);
EverythingApiDllImport.Everything_SetSort(option.SortOption);
EverythingApiDllImport.Everything_SetMatchPath(option.IsFullPathSearch);
if (option.SortOption == EverythingSortOption.RUN_COUNT_DESCENDING)
{
EverythingApiDllImport.Everything_SetRequestFlags(EVERYTHING_REQUEST_FULL_PATH_AND_FILE_NAME | EVERYTHING_REQUEST_RUN_COUNT);
}
else
{
EverythingApiDllImport.Everything_SetRequestFlags(EVERYTHING_REQUEST_FULL_PATH_AND_FILE_NAME);
}
if (token.IsCancellationRequested) yield break;
if (!EverythingApiDllImport.Everything_QueryW(true))
{
CheckAndThrowExceptionOnError();
yield break;
}
for (var idx = 0; idx < EverythingApiDllImport.Everything_GetNumResults(); ++idx)
{
if (token.IsCancellationRequested)
{
yield break;
}
EverythingApiDllImport.Everything_GetResultFullPathNameW(idx, buffer, BufferSize);
var result = new SearchResult
{
// todo the types are wrong. Everything expects uint everywhere, but we send int just above/below. how to fix? Is EverythingApiDllImport autogenerated or handmade?
FullPath = buffer.ToString(),
Type = EverythingApiDllImport.Everything_IsFolderResult(idx) ? ResultType.Folder :
EverythingApiDllImport.Everything_IsFileResult(idx) ? ResultType.File :
ResultType.Volume,
Score = (int)EverythingApiDllImport.Everything_GetResultRunCount( (uint)idx)
};
yield return result;
}
}
finally
{
EverythingApiDllImport.Everything_Reset();
_semaphore.Release();
}
}
private static void CheckAndThrowExceptionOnError()
{
switch (EverythingApiDllImport.Everything_GetLastError())
{
case StateCode.CreateThreadError:
throw new CreateThreadException();
case StateCode.CreateWindowError:
throw new CreateWindowException();
case StateCode.InvalidCallError:
throw new InvalidCallException();
case StateCode.InvalidIndexError:
throw new InvalidIndexException();
case StateCode.IPCError:
throw new IPCErrorException();
case StateCode.MemoryError:
throw new MemoryErrorException();
case StateCode.RegisterClassExError:
throw new RegisterClassExException();
case StateCode.OK:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public static async Task IncrementRunCounterAsync(string fileOrFolder)
{
await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
try
{
_ = EverythingApiDllImport.Everything_IncRunCountFromFileName(fileOrFolder);
}
catch (Exception)
{
/*ignored*/
}
finally { _semaphore.Release(); }
}
}
}