using Flow.Launcher.Plugin.Everything.Everything; 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; /// /// Checks whether the sort option is Fast Sort. /// public static bool IsFastSortOption(SortOption 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 IsEverythingRunningAsync(CancellationToken token = default) { await _semaphore.WaitAsync(token); try { EverythingApiDllImport.Everything_GetMajorVersion(); var result = EverythingApiDllImport.Everything_GetLastError() != StateCode.IPCError; return result; } finally { _semaphore.Release(); } } /// /// Searches the specified key word and reset the everything API afterwards /// /// Search Criteria /// when cancelled the current search will stop and exit (and would not reset) /// An IAsyncEnumerable that will enumerate all results searched by the specific query and option public static async IAsyncEnumerable 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(token); 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 == SortOption.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(); } } } }