From bb1f1a3cc4a780a6e14e41f80871b99aa00736d7 Mon Sep 17 00:00:00 2001 From: xungeng Date: Fri, 9 Oct 2026 19:48:36 +0800 Subject: [PATCH] wlocate: rewrited voidtools everything --- .gitignore | 5 + Build.ps1 | 13 + CMakeLists.txt | 50 +++ Install.ps1 | 51 +++ PERFORMANCE.md | 91 +++++ Package.ps1 | 12 + README.md | 114 ++++++ THIRD-PARTY-NOTICES.md | 39 ++ WORKLOG.md | 144 ++++++++ assets/wlocate-16.png | Bin 0 -> 708 bytes assets/wlocate-256.png | Bin 0 -> 21069 bytes assets/wlocate-32.png | Bin 0 -> 1799 bytes assets/wlocate.ico | Bin 0 -> 45918 bytes assets/wlocate.svg | 11 + include/app.hpp | 11 + include/common.hpp | 94 +++++ include/index.hpp | 80 ++++ include/ipc.hpp | 8 + include/ntfs.hpp | 42 +++ include/query.hpp | 40 ++ include/regex.hpp | 14 + include/resource.h | 11 + include/shell.hpp | 17 + licenses/PCRE2-LICENCE.md | 104 ++++++ licenses/SLJIT-LICENSE.txt | 25 ++ licenses/SwiftList-LICENSE.txt | 21 ++ src/app.manifest | 13 + src/app.rc | 47 +++ src/cli.cpp | 131 +++++++ src/common.cpp | 90 +++++ src/gui.cpp | 612 +++++++++++++++++++++++++++++++ src/gui_accessibility.hpp | 204 +++++++++++ src/gui_hotkeys.hpp | 173 +++++++++ src/gui_icons.hpp | 150 ++++++++ src/index.cpp | 519 ++++++++++++++++++++++++++ src/ipc.cpp | 177 +++++++++ src/main.cpp | 26 ++ src/ntfs.cpp | 296 +++++++++++++++ src/ntfs_parser.cpp | 101 +++++ src/query.cpp | 383 +++++++++++++++++++ src/regex.cpp | 40 ++ src/service.cpp | 209 +++++++++++ src/shell.cpp | 134 +++++++ src/store.cpp | 90 +++++ tests/benchmark.py | 41 +++ tests/gui_cache_tests.cpp | 222 +++++++++++ tests/gui_hotkey_tests.cpp | 90 +++++ tests/hotplug.ps1 | 52 +++ tests/icon_cache_tests.cpp | 77 ++++ tests/integration.py | 103 ++++++ tests/path_benchmark.py | 48 +++ tests/performance.py | 39 ++ tests/protocol.py | 69 ++++ tests/relative_path_benchmark.py | 52 +++ tests/restart.ps1 | 27 ++ tests/tests.cpp | 352 ++++++++++++++++++ tests/ui_tests.cpp | 12 + third_party/pcre2 | 1 + tools/Build-Icon.ps1 | 70 ++++ 59 files changed, 5647 insertions(+) create mode 100644 .gitignore create mode 100644 Build.ps1 create mode 100644 CMakeLists.txt create mode 100644 Install.ps1 create mode 100644 PERFORMANCE.md create mode 100644 Package.ps1 create mode 100644 README.md create mode 100644 THIRD-PARTY-NOTICES.md create mode 100644 WORKLOG.md create mode 100644 assets/wlocate-16.png create mode 100644 assets/wlocate-256.png create mode 100644 assets/wlocate-32.png create mode 100644 assets/wlocate.ico create mode 100644 assets/wlocate.svg create mode 100644 include/app.hpp create mode 100644 include/common.hpp create mode 100644 include/index.hpp create mode 100644 include/ipc.hpp create mode 100644 include/ntfs.hpp create mode 100644 include/query.hpp create mode 100644 include/regex.hpp create mode 100644 include/resource.h create mode 100644 include/shell.hpp create mode 100644 licenses/PCRE2-LICENCE.md create mode 100644 licenses/SLJIT-LICENSE.txt create mode 100644 licenses/SwiftList-LICENSE.txt create mode 100644 src/app.manifest create mode 100644 src/app.rc create mode 100644 src/cli.cpp create mode 100644 src/common.cpp create mode 100644 src/gui.cpp create mode 100644 src/gui_accessibility.hpp create mode 100644 src/gui_hotkeys.hpp create mode 100644 src/gui_icons.hpp create mode 100644 src/index.cpp create mode 100644 src/ipc.cpp create mode 100644 src/main.cpp create mode 100644 src/ntfs.cpp create mode 100644 src/ntfs_parser.cpp create mode 100644 src/query.cpp create mode 100644 src/regex.cpp create mode 100644 src/service.cpp create mode 100644 src/shell.cpp create mode 100644 src/store.cpp create mode 100644 tests/benchmark.py create mode 100644 tests/gui_cache_tests.cpp create mode 100644 tests/gui_hotkey_tests.cpp create mode 100644 tests/hotplug.ps1 create mode 100644 tests/icon_cache_tests.cpp create mode 100644 tests/integration.py create mode 100644 tests/path_benchmark.py create mode 100644 tests/performance.py create mode 100644 tests/protocol.py create mode 100644 tests/relative_path_benchmark.py create mode 100644 tests/restart.ps1 create mode 100644 tests/tests.cpp create mode 100644 tests/ui_tests.cpp create mode 160000 third_party/pcre2 create mode 100644 tools/Build-Icon.ps1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32b16a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/build/ +/out/ +/test-output/ +*.user +*.log diff --git a/Build.ps1 b/Build.ps1 new file mode 100644 index 0000000..f6605be --- /dev/null +++ b/Build.ps1 @@ -0,0 +1,13 @@ +param([string]$Configuration = 'Release', [string]$Generator = 'Visual Studio 18 2026') +$ErrorActionPreference = 'Stop' +if (-not (Test-Path "$PSScriptRoot\third_party\pcre2\deps\sljit\sljit_src\sljitLir.c")) { + git -C "$PSScriptRoot\third_party\pcre2" submodule update --init --depth 1 + if ($LASTEXITCODE) { throw 'Could not initialize the pinned SLJIT dependency.' } +} +cmake -S $PSScriptRoot -B "$PSScriptRoot\build" -G $Generator -A x64 +if ($LASTEXITCODE) { throw 'CMake configuration failed.' } +cmake --build "$PSScriptRoot\build" --config $Configuration --parallel +if ($LASTEXITCODE) { throw 'Build failed.' } +ctest --test-dir "$PSScriptRoot\build" -C $Configuration --output-on-failure +if ($LASTEXITCODE) { throw 'Core regression tests failed.' } +Get-ChildItem "$PSScriptRoot\build\bin\$Configuration\*.exe" diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..45a2426 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.25) +project(WLocate VERSION 1.0.0 LANGUAGES C CXX RC) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +add_compile_definitions(UNICODE _UNICODE NOMINMAX WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0A00) +add_compile_options(/W4 /utf-8 $<$:/permissive-> $<$:/EHsc> $<$:/O2>) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(PCRE2_BUILD_PCRE2_8 ON CACHE BOOL "" FORCE) +set(PCRE2_BUILD_PCRE2_16 OFF CACHE BOOL "" FORCE) +set(PCRE2_BUILD_PCRE2_32 OFF CACHE BOOL "" FORCE) +set(PCRE2_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(PCRE2_BUILD_PCRE2GREP OFF CACHE BOOL "" FORCE) +set(PCRE2_SUPPORT_JIT ON CACHE BOOL "" FORCE) +add_subdirectory(third_party/pcre2 EXCLUDE_FROM_ALL) +add_library(wlocate_core STATIC + src/common.cpp src/query.cpp src/regex.cpp src/index.cpp src/ntfs.cpp src/ntfs_parser.cpp src/store.cpp src/ipc.cpp) +target_include_directories(wlocate_core PUBLIC include) +target_link_libraries(wlocate_core PUBLIC advapi32 shell32 ole32 shlwapi bcrypt pcre2-8-static) +add_executable(wlocate WIN32 src/main.cpp src/service.cpp src/gui.cpp src/shell.cpp src/app.rc) +target_link_options(wlocate PRIVATE /MANIFEST:NO) +target_link_libraries(wlocate PRIVATE wlocate_core comctl32 comdlg32 oleaut32 uuid uxtheme) +target_link_libraries(wlocate PRIVATE windowscodecs) +add_executable(locate src/cli.cpp src/app.rc) +target_link_options(locate PRIVATE /MANIFEST:NO) +target_link_libraries(locate PRIVATE wlocate_core) +add_executable(wlocate_tests tests/tests.cpp src/app.rc) +target_link_options(wlocate_tests PRIVATE /MANIFEST:NO) +target_link_libraries(wlocate_tests PRIVATE wlocate_core) +enable_testing() +add_test(NAME core COMMAND wlocate_tests) +add_executable(wlocate_ui_tests WIN32 tests/ui_tests.cpp src/gui.cpp src/shell.cpp src/app.rc) +target_link_options(wlocate_ui_tests PRIVATE /MANIFEST:NO) +target_link_libraries(wlocate_ui_tests PRIVATE wlocate_core comctl32 comdlg32 oleaut32 uuid uxtheme windowscodecs) +add_executable(wlocate_gui_cache_tests tests/gui_cache_tests.cpp src/shell.cpp src/app.rc) +target_link_options(wlocate_gui_cache_tests PRIVATE /MANIFEST:NO) +target_link_libraries(wlocate_gui_cache_tests PRIVATE wlocate_core comctl32 comdlg32 oleaut32 uuid uxtheme windowscodecs) +add_test(NAME gui_cache COMMAND wlocate_gui_cache_tests) +add_executable(wlocate_icon_cache_tests tests/icon_cache_tests.cpp src/app.rc) +target_link_options(wlocate_icon_cache_tests PRIVATE /MANIFEST:NO) +target_link_libraries(wlocate_icon_cache_tests PRIVATE wlocate_core comctl32) +add_test(NAME icon_cache COMMAND wlocate_icon_cache_tests) +add_executable(wlocate_hotkey_tests tests/gui_hotkey_tests.cpp src/shell.cpp src/app.rc) +target_link_options(wlocate_hotkey_tests PRIVATE /MANIFEST:NO) +target_link_libraries(wlocate_hotkey_tests PRIVATE wlocate_core comctl32 comdlg32 oleaut32 uuid uxtheme windowscodecs) +add_test(NAME hotkey COMMAND wlocate_hotkey_tests) +foreach(gui_target wlocate wlocate_ui_tests wlocate_gui_cache_tests wlocate_hotkey_tests) + target_link_libraries(${gui_target} PRIVATE uiautomationcore) +endforeach() +set_target_properties(wlocate locate PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") diff --git a/Install.ps1 b/Install.ps1 new file mode 100644 index 0000000..e550aa7 --- /dev/null +++ b/Install.ps1 @@ -0,0 +1,51 @@ +param( + [string]$BuildDirectory = "$PSScriptRoot\build\bin\Release", + [string]$InstallDirectory = "$env:ProgramFiles\WLocate", + [string]$CliDirectory = 'D:\tools\bin', + [switch]$StageOnly +) +$ErrorActionPreference = 'Stop' +if (-not (Test-Path -LiteralPath (Join-Path $BuildDirectory 'wlocate.exe')) -and (Test-Path -LiteralPath "$PSScriptRoot\wlocate.exe")) { $BuildDirectory = $PSScriptRoot } +Start-Transcript -Path "$PSScriptRoot\install-transcript.log" -Append | Out-Null +$identity = [Security.Principal.WindowsIdentity]::GetCurrent() +$principal = [Security.Principal.WindowsPrincipal]::new($identity) +if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Run this installer with psuac or from an elevated PowerShell.' } +$serviceExe = Join-Path $BuildDirectory 'wlocate.exe' +$cliExe = Join-Path $BuildDirectory 'locate.exe' +if (-not (Test-Path -LiteralPath $serviceExe) -or -not (Test-Path -LiteralPath $cliExe)) { throw 'Build both release executables before installing.' } +$existing = Get-Service -Name WLocateService -ErrorAction SilentlyContinue +if ($existing -and $existing.Status -ne 'Stopped') { Stop-Service -Name WLocateService; $existing.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(120)) } +New-Item -ItemType Directory -Path $InstallDirectory -Force | Out-Null +Copy-Item -LiteralPath $serviceExe -Destination (Join-Path $InstallDirectory 'wlocate.exe') -Force +Copy-Item -LiteralPath $cliExe -Destination (Join-Path $InstallDirectory 'locate.exe') -Force +foreach ($document in @('README.md', 'THIRD-PARTY-NOTICES.md', 'PERFORMANCE.md')) { + if (Test-Path -LiteralPath (Join-Path $PSScriptRoot $document)) { Copy-Item -LiteralPath (Join-Path $PSScriptRoot $document) -Destination $InstallDirectory -Force } +} +if (Test-Path -LiteralPath "$PSScriptRoot\licenses") { Copy-Item -LiteralPath "$PSScriptRoot\licenses" -Destination $InstallDirectory -Recurse -Force } +$install = Start-Process -FilePath (Join-Path $InstallDirectory 'wlocate.exe') -ArgumentList '--install-service' -WindowStyle Hidden -PassThru -Wait +if ($install.ExitCode -ne 0) { throw "Service installation failed with exit code $($install.ExitCode); see ProgramData\WLocate\service.log" } +if (-not $StageOnly) { + New-Item -ItemType Directory -Path $CliDirectory -Force | Out-Null + $destination = Join-Path $CliDirectory 'locate.exe' + $backup = $null + if (Test-Path -LiteralPath $destination) { $backup = "$destination.backup-$(Get-Date -Format yyyyMMdd-HHmmss)"; Copy-Item -LiteralPath $destination -Destination $backup } + $rollback = $backup + $statePath = Join-Path $InstallDirectory 'install-state.json' + if (Test-Path -LiteralPath $statePath) { + $previous = Get-Content -LiteralPath $statePath -Raw | ConvertFrom-Json + if ($previous.locate -eq $destination -and $previous.previous_locate -and (Test-Path -LiteralPath $previous.previous_locate)) { $rollback = $previous.previous_locate } + } + Copy-Item -LiteralPath $cliExe -Destination $destination -Force + Copy-Item -LiteralPath $serviceExe -Destination (Join-Path $CliDirectory 'wlocate.exe') -Force + $shell = New-Object -ComObject WScript.Shell + $shortcut = $shell.CreateShortcut("$env:ProgramData\Microsoft\Windows\Start Menu\Programs\WLocate.lnk") + $shortcut.TargetPath = Join-Path $InstallDirectory 'wlocate.exe' + $shortcut.WorkingDirectory = $InstallDirectory + $shortcut.Description = 'WLocate 文件查找' + $shortcut.IconLocation = (Join-Path $InstallDirectory 'wlocate.exe') + ',0' + $shortcut.Save() + @{ installed_at = (Get-Date -Format o); previous_locate = $rollback; last_update_backup = $backup; locate = $destination; gui = (Join-Path $InstallDirectory 'wlocate.exe') } | ConvertTo-Json | Set-Content -LiteralPath $statePath -Encoding utf8 +} +Get-Service WLocateService | Format-Table Name, Status +Get-FileHash -LiteralPath (Join-Path $InstallDirectory 'wlocate.exe'), (Join-Path $InstallDirectory 'locate.exe') -Algorithm SHA256 +Stop-Transcript | Out-Null diff --git a/PERFORMANCE.md b/PERFORMANCE.md new file mode 100644 index 0000000..c105613 --- /dev/null +++ b/PERFORMANCE.md @@ -0,0 +1,91 @@ +# 本机验证结果 + +测量日期:2026-10-07 至 2026-10-08。Windows x64,C: 和 D: 合计约 327 万条文件/目录记录。数字会随本机文件变化、系统负载和缓存状态波动。 + +## 相对路径片段查询优化(2026-10-08) + +`foo\bar` 没有盘符,也没有通配符,因此此前的绝对路径通配符优化没有覆盖它。修复前依然逐项重建完整路径;同样 17 个结果,原生命令行约 3779 ms,旧包装器约 253 ms。现在根据片段的最后一个组件在名称索引中找候选项,验证前面的目录组件,再遍历匹配目录的子树。`foo\bar` 允许 `afoo\barista` 这样的原有子串匹配,也包含匹配目录下的后代;不会把查询错误地改成组件精确匹配。 + +四次运行排除首次后的三次中位数,索引已在内存,GUI 同等查询每次清除上次结果缓存。命令行总耗时包含进程启动、IPC 和接收完整 stdout;GUI 列只测服务,不含输入防抖、绘制和图标。脚本:`tests/relative_path_benchmark.py`;原始数据:`test-output/relative-path-before.json`、`relative-path-after.json`。 + +| 查询 | 原生修复前总耗时 | 旧包装器本轮总耗时 | 原生修复后总耗时 | GUI 同等查询服务耗时 | 相同结果数 | +|---|---:|---:|---:|---:|---:| +| `foo\bar` | 3779.2 ms | 249.0 ms | **17.3 ms** | 1.374 ms | 17 | +| `Windows\Logs` | 3437.1 ms | 218.9 ms | **18.9 ms** | 1.490 ms | 364 | +| `Windows\Logs\` | 3382.0 ms | 247.7 ms | **14.7 ms** | 1.340 ms | 363 | +| `Windows\System32\drivers\etc` | 3423.7 ms | 235.9 ms | **17.6 ms** | 0.602 ms | 7 | + +四种查询的旧包装器、原生 CLI 和 GUI 同等查询均逐路径比较一致。这里仍比较本机旧包装器,不代表测出了 Everything 搜索引擎本身的耗时或内部实现。 + +该方案复用已有名称前缀索引、父目录关系和子项链表,不增加常驻索引或重复的完整路径缓存。只有单组件加结尾斜杠(如 `foo\`)需要做名称后缀预筛。支持每个 OR 分支带正向路径片段的查询、附加筛选条件、大小写、Unicode、目录改名/移动和实时新增名称;无法安全缩小范围的查询继续通用匹配。2386 项核心断言及现有 GUI/图标测试通过,其中相对路径优化用逐项完整路径匹配作独立候选筛选对照,并验证 30000 个无关名称不会触发全扫描。实际磁盘集成测试同时比较名称查询、绝对路径通配符和相对路径片段,全部通过。 + +## 固定目录前缀的路径通配符优化(22:51 更新) + +用户报告 `C:\Windows\Logs\*.log` 明显慢于旧版。修复前,路径通配符没有使用目录范围,每次对全盘约 327 万条记录重建完整路径并匹配;GUI 还遍历了全局名称排序。新增按父文件引用号维护的子项链表,先解析固定目录前缀,再遍历对应子树;目录移动/改名、硬链接变化、快照恢复和压缩都会维护或重建此索引。 + +同一台电脑、内存索引已就绪,6 次运行排除首次后的 5 次中位数。原始结果见 `test-output/path-before.json`、`test-output/path-after.json`;复测脚本 `tests/path_benchmark.py`。 + +| 查询 `C:\Windows\Logs\*.log` | 修复前 | 修复后 | +|---|---:|---:| +| 旧 `locate.old.exe` 启动至退出 | 152.8 ms | 160.1 ms | +| 原生 `locate.exe` 启动至退出 | 2598.9 ms | 14.7 ms | +| 原生命令行查询的服务内耗时 | 2581.8 ms | 0.486 ms | +| GUI 同等查询:名称排序、全部计数、返回前 256 条,服务内耗时 | 3638.2 ms | 0.571 ms | + +三种查询均返回相同的 62 个路径。GUI 同等查询通过 CLI 的 Everything 语法和名称排序选项提交,每次先用不同查询清除上次结果缓存;这里测量服务查询,不包含 GUI 的 100 ms 输入防抖、界面绘制或图标加载。命令行总耗时包含进程启动、IPC 和接收 stdout,不包含终端绘制。比较对象是本机旧包装器,不能由此推断所有查询都快于 Everything 引擎。 + +保留原有通配符跨子目录、大小写、OR/AND、排除条件、排序和分页语义。只有每个 OR 分支都有正向、带绝对目录前缀的路径通配符时才缩小范围;其他查询沿用通用路径。子项索引增加每行 4 字节及每个父目录一个哈希表项,不保存重复的完整路径。442 项核心断言、30 项 GUI 缓存检查、16 项图标缓存检查通过;实际磁盘集成测试同时验证名称查询与目录范围查询,包括 30 个硬链接、目录改名、文件移动、删除、Unicode 长路径和 500 文件批量创建。 + +以下为此前测量记录。 + +## 同结果的命令行比较 + +命令通过 Python `subprocess` 启动,接收 stdout,测量从启动到退出的总时间;不包含终端逐行绘制。每项运行 9 次,排除首次后报告 8 次中位数。原始数据:`test-output/benchmark.json`。 + +| 命令 | 原来的 locate.old.exe | 原生 locate.exe | 结果 | +|---|---:|---:|---| +| `python*.exe` | 68.86 ms | 9.10 ms | 同样的 66 个完整路径,集合完全一致 | +| 不存在的文件名通配符 | 68.28 ms | 8.65 ms | 都为 0 条 | +| `-n 10 "python*.exe"` | 不支持该参数,不比较 | 8.40 ms | 10 条 | +| `-n 10 "*.exe"` | 不支持该参数,不比较 | 7.61 ms | 10 条 | + +这里比较的是本机旧包装器和新命令行的端到端时间,不能据此声称原生搜索引擎在所有查询上都比 Everything 引擎快。旧版 `*.exe` 输出为 9999 条;原生为 12286 条,这种不同结果规模未用于速度结论。旧版独有的 9 个 Office 更新临时路径,Win32 检查均返回 ERROR_FILE_NOT_FOUND;记录见 `test-output/exe-comparison.json`。 + +## 原生服务内查询时间 + +来自 `test-output/performance.json`。每个查询连续运行三次;“首次”表示该查询没有已完成结果缓存,磁盘索引已经在内存。 + +| 查询 | 服务内时间 | 说明 | +|---|---:|---| +| `-n 10 python*.exe` | 0.30–0.38 ms | 名称前缀索引,找到足够数量就停止 | +| `-bic python` | 26–28 ms | 名称子串计数,10302 条 | +| `-ic python` | 43–52 ms | 完整路径子串计数,84020 条 | +| `-ic -r '^python.*\.exe$'` | 281–309 ms | Unicode PCRE2 JIT 正则,全名称扫描 | +| 全索引按名称排序,第一页 | 首次 1282 ms;缓存约 0.2 ms | 约 320 万条匹配、返回 256 条 | +| 全索引按完整路径排序,第一页 | 首次 3972 ms;缓存约 0.2 ms | 同上 | +| 全索引按大小排序,第一页 | 首次 2325 ms;缓存约 0.4 ms | 同上 | + +最明显的剩余性能差距是大范围查询的首次排序和通用正则扫描。查询缓存会在相关索引变化后失效;名称查询不会因为仅修改文件大小/时间而失效。一次采样的服务工作集约 642 MiB、私有内存约 751 MiB;包含名称、父子关系、硬链接、文件元数据和查询缓存,未达到 Everything 的内存效率。 + +## 实时性、恢复与正确性 + +- 86 项核心断言通过:UTF-8 进程代码页和窄字符文件 API、查询、Unicode、正则工作量限制、日期边界、硬链接、DOS 别名计数、路径排序、分页、取消、快照校验、压缩与部分卷可用。 +- 实际磁盘测试通过:30 个长名称硬链接跨 MFT 扩展记录;目录改名、单链接删除、跨目录移动、所有链接元数据同步、超过 MAX_PATH 的 Unicode 路径。 +- 常规文件更新在测试中约 0.12–0.35 秒可见;500 文件突发创建约 0.12 秒可见。大范围排序占用索引读锁时,更新发布可能推迟。 +- 服务停止后创建文件,重启后约 2143 ms 可查询,验证快照恢复与 USN 补读。 +- 临时 NTFS VHD 插入后测试文件约 131 ms 可查;拔出后约 1787 ms 从已发布卷中移除,其他卷保持可查询。轮询相位会影响这些数字;热插拔检测周期为 2 秒。 +- 60 次并发查询、20 个客户端线程,结果一致;单次 20000 条 / 约 2.1 MB 结果传输通过。 +- 100 ms 请求超时约 104 ms 返回,随后服务继续正常响应;畸形请求和普通用户重建请求被拒绝。 +- 程序内 GUI 测试通过:搜索、OR 分组与类型筛选、虚拟列表翻页、单文件与跨目录多选的系统/第三方 Shell 菜单、真实中文文件重命名。渲染图在 `test-output/ui-final`,已检查布局。 + +GUI 验证使用程序内集成测试和实际窗口渲染;本次外部桌面自动化接口不可用,未把模拟鼠标逐项点击全部第三方菜单当作已完成的测试。网络盘和非 NTFS 卷不在支持或测试范围。 + +## 主要实现选择 + +- 原生 MFT + USN 服务维护索引,CLI/GUI 通过本机管道提交查询,只传回匹配结果。 +- 名称去重、排序前缀查找、64 位三元组 Bloom 预筛、连续内存扫描;路径子串查询复用父目录匹配结果。 +- 目录采用文件引用号及父行缓存,改名无需重写所有子路径;完整 NTFS 序列号避免文件记录重用串到旧文件。 +- PCRE2 JIT、64 KiB 传输缓冲、服务端限量和虚拟列表分页减少不必要的计算与传输。 +- 快照带 SHA-256 校验并原子替换;未完成的记录更新保留日志重放位置,避免服务重启后漏掉未解决的变化。 + +这些优化解决了之前 C# 适配器整批传送目录、客户端重组和筛选的主要开销;性能问题不应简单归因于 C# 这一语言本身。 diff --git a/Package.ps1 b/Package.ps1 new file mode 100644 index 0000000..86c273d --- /dev/null +++ b/Package.ps1 @@ -0,0 +1,12 @@ +$ErrorActionPreference = 'Stop' +$destination = Join-Path $PSScriptRoot 'out\WLocate-1.0.0-win-x64' +New-Item -ItemType Directory -Path $destination -Force | Out-Null +foreach ($name in @('wlocate.exe', 'locate.exe')) { Copy-Item -LiteralPath "$PSScriptRoot\build\bin\Release\$name" -Destination $destination -Force } +foreach ($name in @('README.md', 'PERFORMANCE.md', 'THIRD-PARTY-NOTICES.md', 'Install.ps1')) { Copy-Item -LiteralPath "$PSScriptRoot\$name" -Destination $destination -Force } +Copy-Item -LiteralPath "$PSScriptRoot\licenses" -Destination $destination -Recurse -Force +$hashes = Get-FileHash -LiteralPath "$destination\wlocate.exe", "$destination\locate.exe" -Algorithm SHA256 +$hashes | ForEach-Object { "$($_.Hash.ToLowerInvariant()) $([IO.Path]::GetFileName($_.Path))" } | Set-Content -LiteralPath "$destination\SHA256SUMS.txt" -Encoding ascii +$archive = "$destination.zip" +Compress-Archive -LiteralPath $destination -DestinationPath $archive -CompressionLevel Optimal -Force +Get-Item -LiteralPath $archive +$hashes diff --git a/README.md b/README.md new file mode 100644 index 0000000..15d8449 --- /dev/null +++ b/README.md @@ -0,0 +1,114 @@ +# WLocate 1.0 + +Windows x64 原生文件查找工具。`wlocate.exe` 提供 GUI 和后台服务,`locate.exe` 提供命令行;两者共享独立的 C++ 索引。日常搜索无需管理员权限,不依赖 Everything、SwiftList 或 .NET。 + +两个程序均嵌入 UTF-8 activeCodePage manifest,启动时核验进程代码页为 65001,要求 Windows 10 1903 或更新系统。编译器使用 `/utf-8`;窄字符串、索引、IPC 和重定向文本统一为 UTF-8,在 Windows 窗口、Shell 和 W API 边界转换 UTF-16。它不会更改系统区域设置或父终端代码页。 + +绿色放大镜图标覆盖 EXE、窗口、任务栏、托盘和开始菜单,包含 16–256 像素共九种尺寸。可编辑矢量稿在 `assets/wlocate.svg`,运行 `tools/Build-Icon.ps1` 可重新生成 ICO。 + +## 本机安装与使用 + +- GUI / 服务程序:`C:\Program Files\WLocate\wlocate.exe`。 +- 命令行:`D:\tools\bin\locate.exe`;同目录的 `wlocate.exe` 可直接启动界面。 +- 开始菜单:WLocate。后台服务:`WLocateService`,自动启动。 +- 索引和日志:`C:\ProgramData\WLocate`。界面设置:`%LOCALAPPDATA%\WLocate\settings.ini`。 +- 原来的 `locate.old.exe` 保留。此次替换前的 locate 备份路径记录于 `C:\Program Files\WLocate\install-state.json`。 + +```powershell +locate -n 10 "python*.exe" +locate -i -b python +locate -ic "*.dll" +locate -0 -n 100 "*.cpp" +locate -r '^python.*\.exe$' +locate --query 'ext:exe size:>1mb !path:Windows' --sort size --descending -n 20 +locate -S +wlocate "python*.exe" +``` + +## 已实现的功能 + +| 部分 | 行为 | +|---|---| +| 建索引 | 自动发现带盘符的本地 NTFS 卷;直接读取 MFT、扩展记录及长文件名硬链接;索引名称、父目录、大小、时间、属性 | +| 实时更新 | USN 日志增量更新,约 250 ms 轮询;创建、删除、改名、移动、硬链接及元数据变化;目录改名不必重写所有后代路径 | +| 持久化 | 原子替换的 SHA-256 校验快照;重启后续读 USN;卷标识或日志缺口不匹配时重建;临时繁忙记录保留旧值并重试 | +| 磁盘变化 | 自动加入新 NTFS 盘、移除拔出的盘;一个卷建索引时仍可查询其他就绪卷 | +| 查询 | Unicode、大小写开关、通配符、PCRE2 JIT 正则、AND/OR/NOT 和分组、常用 Everything 过滤函数 | +| 界面 | 输入即时搜索、类型筛选、名称/完整路径/大小/修改时间/扩展名排序、虚拟列表分页、书签、CSV/文本导出、托盘、自定义全局快捷键、可选登录启动 | +| 文件操作 | 原生 Windows Shell 右键菜单和已安装的扩展;打开、所在文件夹、复制、剪切、删除、属性、重命名、拖出;支持跨目录多选 | +| 命令行 | 服务端限量、计数、NUL 分隔、存在性检查、超时与 Ctrl+C 取消、UTF-8 重定向输出 | + +程序自身不提升普通 GUI 的权限。安装服务和重建全盘索引才需要管理员权限;Shell 文件操作遵循当前用户的 Windows 权限和系统确认行为。 + +## 界面语法 + +默认忽略大小写、匹配名称。空格为 AND,`|` 为 OR,`!` 为 NOT,`<...>` 分组,双引号包含空格或运算符。 + +```text +python*.exe + ext:exe +"Program Files" +file: ext:cpp;h !path:build +folder: path:D:\work +size:>10mb dm:today +size:1kb..10mb dc:2026-01-01..2026-12-31 +attrib:h +case:Python +regex:"^python(?:3)?\.exe$" +``` + +支持 `file:`、`folder:`、`ext:`、`path:`、`parent:`、`size:`、`dm:`/`datemodified:`、`dc:`/`datecreated:`、`da:`/`dateaccessed:`、`attrib:`/`attributes:`、`case:`、`nocase:`、`regex:`、`ww:`/`wholeword:`。属性字母:`h s r a d l`。日期支持 `YYYY-MM-DD`、`today`、`yesterday`、范围和比较运算;按本地日历边界解释。大小单位使用 1024 倍数。完整路径正则以 `/` 作为路径分隔符。 + +| 快捷键 | 功能 | +|---|---| +| Ctrl+L / Ctrl+F | 定位搜索框 | +| Ctrl+Alt+L(默认,可修改) | 界面已运行时显示窗口 | +| Enter / Ctrl+Enter / Alt+Enter | 打开 / 所在文件夹 / 属性 | +| Ctrl+C / Ctrl+X / Ctrl+Shift+C | 复制文件 / 剪切文件 / 复制完整路径 | +| F2 / Delete / Shift+Delete | 重命名 / 系统删除 / 系统永久删除 | +| Ctrl+A / F5 | 全选 / 刷新 | +| Ctrl+S / Ctrl+D | 导出全部匹配结果 / 收藏当前查询 | + +全局唤出快捷键可在 **工具 → 快捷键设置** 或托盘右键菜单中修改。选择 Ctrl / Alt / Shift / Win 组合与按键,点击“确定”立即生效并保存,下次启动继续使用;也可禁用或恢复默认。新组合被占用、系统不允许注册或设置保存失败时,原快捷键保持可用,窗口内会提示原因。为避免拦截日常输入,普通按键需搭配 Ctrl、Alt 或 Win;功能键可单独使用。程序启动时遇到快捷键冲突会给出托盘提示,仍可通过托盘打开窗口修改。 + +导出先写临时文件,完成后替换目标;CSV 使用 UTF-8 BOM。界面每两秒刷新;有选中项或正在重命名时暂停自动刷新,以免选中对象移动。搜索框清空可显示全部索引。列宽、窗口大小、查询开关及最多 30 条书签会保存。 + +## 命令行兼容范围 + +`locate --help` 列出完整选项。支持 Linux locate 常用参数 `-n/-l`、`-i`、`-b`、`-w`、`-A/-a`、`-c`、`-0`、`-e`、`-E`、`-r`、`--regex`、`-S` 和 `--`。 + +这是 Windows 索引客户端,不读取 GNU/mlocate/plocate 数据库,未实现 `updatedb`、`--database` 或全部 GNU 扩展。默认区分大小写;普通文字按完整路径子串匹配,未带分隔符的通配符和正则按名称匹配,`-w` 强制匹配完整路径,`-b` 强制名称匹配。多个模式默认 OR,`-A` 改为 AND;`--query` 使用界面语法并默认忽略大小写。正则语法为 PCRE2。 + +无排序的 `-n` 在后端找到足够条目即停止;结果顺序不保证稳定。需要确定顺序时加 `--sort`。`-c -n 10` 的计数最多为 10。退出码:0 有结果,1 无结果,2 错误,130 被取消。索引尚未全部就绪时可能返回部分结果,同时在 stderr 提示并以 2 退出。 + +## 编译、安装、回退 + +使用 MSVC、Windows SDK、CMake 3.25+;本机验证环境为 VS 2026、MSVC 19.50、CMake 4.2.3。依赖源已固定在 `third_party/pcre2`,需要其 SLJIT 子模块。 + +```powershell +.\Build.ps1 +# 以管理员身份运行;在这台电脑可以使用 psuac: +psuac -w powershell.exe -NoProfile -ExecutionPolicy Bypass -File D:\work\gitprojs\swiftlist-locate\wlocate\Install.ps1 +``` + +源码构建输出在 `build\bin\Release`。发布包内可直接运行其 `Install.ps1`,或指定 `-BuildDirectory`;`-CliDirectory` 指定命令行目录,默认 `D:\tools\bin`。`-StageOnly` 仅安装服务端及 Program Files 内的程序,不替换 PATH 中的 locate。更新前先退出 WLocate 界面。 + +`wlocate --install-service` 在提升权限后会将服务程序安装到 Program Files;不会让 SYSTEM 服务从普通用户可写的工具目录运行。安装目录及数据目录仅允许管理员/SYSTEM 写入。 + +需要回退命令行时,将 `install-state.json` 中 `previous_locate` 对应的文件复制回 `D:\tools\bin\locate.exe`。原始 Everything 客户端 `D:\tools\bin\locate.old.exe` 也一直保留。需要删除服务时,以管理员运行 `"C:\Program Files\WLocate\wlocate.exe" --uninstall-service`。此操作保留索引、用户设置和二进制文件,便于重新安装。 + +## 验证与性能 + +`tests/tests.cpp` 覆盖查询语法、Unicode、范围、正则边界、硬链接、路径/分页排序、取消、元数据缓存、MFT 解析、校验快照、压缩及部分卷可用。 + +`tests/integration.py` 使用工作区内独立 UUID 目录测试实时更新、30 个硬链接跨 MFT 扩展记录、Unicode 长路径、500 文件突发创建、元数据和清理。`tests/protocol.py` 测试畸形请求、60 次并发查询、2 万条输出、超时及未提升权限的重建拒绝。`tests/restart.ps1` 和 `tests/hotplug.ps1` 需要管理员权限;后者只格式化自己新建的临时 VHD,不选择物理磁盘。`wlocate_ui_tests.exe` 创建自己的窗口和测试文件,验证筛选、虚拟列表、跨目录原生菜单及 Unicode 重命名,并保存渲染图。 + +性能数据与测量口径见 `PERFORMANCE.md`,原始记录在源码目录的 `test-output`。旧版包装器不支持 `-n`;本次 `*.exe` 对比中旧版仅输出 9999 条,而原生版找到 12286 条,因此不能把它的限量/大范围输出直接当作等价性能基准。 + +## 当前边界 + +本版交付了本地 NTFS 文件名搜索的完整常用流程,并未实现 Everything 的全部功能,也不保证所有查询都达到其性能。目前不支持无盘符卷、ReFS/FAT/exFAT/网络共享的文件夹索引、内容搜索、ETP/HTTP 服务、Everything 插件/IPC 兼容接口、高级预览及所有搜索函数;未知函数会明确报错。目录大小不递归统计,重解析点目标不会展开为额外的别名路径。仅测试 Windows x64。 + +第一次全盘扫描、数百万条结果的首次全量排序仍可能需要数秒;首次建索引更久。快照缓存后的启动和常用限量名称查询要快得多。索引中的文件名可被本机经过身份验证的用户查询,和文件内容读取权限是两回事;文件打开和更改仍由 Windows ACL 控制。暂时无法读取的记录会显示为统计中的 `pending` 并重试。 + +许可证和参考出处见 `THIRD-PARTY-NOTICES.md` 与 `licenses`。 diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md new file mode 100644 index 0000000..abb007c --- /dev/null +++ b/THIRD-PARTY-NOTICES.md @@ -0,0 +1,39 @@ +# Third-party notices and references + +WLocate is a native C++ implementation. It does not link to, launch, or require +Everything, SwiftList, the Everything service, or the .NET runtime. + +The binary statically includes PCRE2 10.49, upstream commit +`6f9d7c1373262c541324a16a358785b33ef116cf`, and its pinned SLJIT dependency +`de0259c7aaf36aa40cba8014f3fad3edde9307f9`. +Their complete redistribution notices are in `licenses/PCRE2-LICENCE.md` and +`licenses/SLJIT-LICENSE.txt`. Sources: +and . + +Architectural references: + +- SwiftList, MIT licensed: , local + reference commit `ef39db8c2aac8507130b789d5448f5305c5dfd27`. Its notice is + included in `licenses/SwiftList-LICENSE.txt` as reference attribution. +- Everything search syntax: . +- Everything service model: . +- Everything IPC documentation: . +- Microsoft NTFS, USN, Windows services and Shell API documentation. + +The user's installed Everything 1.4.1.1032 was inspected locally with IDA MCP. +The reference executable's SHA-256 is +`F191F756996A14A11E5445FA7103D302EFD510CF2FBF920E6C0C8ED51D512E36`. +Observed query dispatch routines at `0x1400af3b0` and `0x1400ae4b0` partition +file/folder work and combine results; the NTFS record-reading routine is at +`0x1400cfe80`. These observations informed architectural choices. WLocate's +source is independently implemented; the Everything executable, SDK binary +and decompiled code are not included in the release package. No affiliation +with or endorsement by Voidtools or SwiftList is implied. + +WLocate's green magnifying-glass icon is original vector artwork inspired by +Everything's magnifying-glass silhouette. Editable SVG and the reproducible +multi-resolution ICO generator are included in the source tree. + +The UTF-8 process manifest follows the user's `D:\work\dev3\utillib\utf8.manifest` +and Microsoft's guidance: +. diff --git a/WORKLOG.md b/WORKLOG.md new file mode 100644 index 0000000..8ab387f --- /dev/null +++ b/WORKLOG.md @@ -0,0 +1,144 @@ +# WLocate implementation record + +## Feature — configurable global window hotkey, 2026-10-08 + +- Added native “工具 → 快捷键设置” dialog and the same entry in the tray context menu. Users can choose Ctrl/Alt/Shift/Win modifiers and letters, digits, function/navigation/punctuation/numpad keys, disable the shortcut, or restore the Ctrl+Alt+L default. Changes apply on OK and persist immediately; Cancel/default preview do not alter active settings. Existing installations retain the old default until changed. +- `gui_hotkeys.hpp` owns registration and one `[Keyboard] ShowWindow` INI value for enabled/modifiers/key. A replacement is registered under an alternate ID before releasing the active registration; occupied/reserved combinations or a failed settings write leave the old binding and saved value intact. Disable unregisters, restart loads saved preferences, malformed settings fall back to the default. Startup registration failures are shown in the tray notification/tooltip and settings dialog. Tray tooltip/About show the configured shortcut instead of hard-coded text. WM_HOTKEY validates current ID and chord and brings an already-open modal dialog forward. +- Native dialog uses the shared UTF-8 resource build and Windows dialog font/DPI scaling. Bare typing keys require Ctrl/Alt/Win; function keys can stand alone. F12 is excluded per Windows' documented debugger reservation. References: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey and https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-hotkey . +- Added 32 checks using actual RegisterHotKey ownership and a separate temporary INI: default/custom/disabled reload, invalid configuration, switching/releasing keys, unchanged selection, conflicts, failed-save rollback, startup failure and retry, dialog default/cancel/OK/disabled controls, inline conflict message. Tests reserve unused Ctrl+Alt+Shift+F13–F24 combinations and send no keyboard input to the desktop. Default UI integration tests no longer try to register the user's global shortcut. Dialog/conflict renders inspected at `test-output/hotkey-ui/hotkey-settings.png` and `hotkey-conflict.png`. +- Release build and all four CTest targets pass. Program Files/PATH executables, installed README and release ZIP refreshed; verified hashes in `test-output/installation-hotkey.json`. User's chosen settings are preserved; GUI restarted hidden, retaining the configured/default global shortcut. + +## Fix — relative literal path fragment planning, 2026-10-08 + +- User reported `locate.old.exe foo\bar` was much faster. Reproduced with identical 17 paths: old wrapper 252.5 ms, native CLI 3779.2 ms (3765.1 ms service), GUI-equivalent uncached query 4866.1 ms service. The previous absolute-glob bound could not apply to literal relative fragments, which still reconstructed every indexed full path. +- `Query::path_literals()` chooses a necessary positive literal path term in every OR branch. `Volume::find_literals()` uses the last component as a name-prefix candidate range (exact component for a trailing separator), verifies the preceding path suffix, and walks matching directory descendants with row deduplication. A lone `foo\` uses Bloom-assisted name-suffix filtering. Actual predicates still enforce case, whole word, OR/AND, negation and metadata. Root/drive fragments, Unicode, partial first/last components, hardlink rows, late unique names and overlapping seed subtrees retain previous semantics. General matching remains the fallback when a branch is unbounded. Existing absolute-glob planning stays ahead of this path. +- No persistent index or snapshot format changes: reuse sorted names, new-name delta chains and parent/child relationships. Temporary row visitation bytes are allocated only when candidate seeds exist. Directory/name generation invalidates cached results as before. +- Added exhaustive full-path oracle comparisons across relative/absolute literals, slash forms, trailing separators, case collisions, Unicode, whole-word/OR/metadata, multi-pattern CLI, ordering, count-only, limits and GUI pagination. Generated fragments cross arbitrary component boundaries. Mutation checks cover directory moves/renames, changed hardlinks, orphan/late parents, deletions, compaction and snapshot restore. Bounded-work checks use 30000 unrelated names. All three CTest targets pass: 2386 core checks, existing 30 GUI cache and 16 icon cache checks. +- Extended live integration to compare all three query paths (name, absolute glob subtree, relative literal subtree) after create/rename/move/delete, 30 hardlinks, metadata changes, long Unicode paths and 500-file bursts; all passed. Logs: `test-output/build-relative-path.log`, `ctest-relative-path.log`, `integration-relative-path.log`. +- Deployed service benchmark: `foo\bar` native median 17.2717 ms total / 1.193 ms service, old wrapper 248.9998 ms total; uncached GUI-equivalent 1.374 ms service. `Windows\Logs`: 18.8648 vs 218.8839 ms, 364 identical paths; trailing slash: 14.6512 vs 247.6539 ms, 363 paths; `Windows\System32\drivers\etc`: 17.5668 vs 235.9423 ms, 7 paths. All four complete result sets agree among old/native/GUI. Four runs with first excluded, warm resident index; no GUI rendering/debounce included. Data and reusable script: `test-output/relative-path-{before,after}.json`, `tests/relative_path_benchmark.py`. +- Installed Program Files/PATH binaries, updated installed performance notes and release ZIP, verified hashes in `test-output/installation-relative-path.json`. GUI restarted hidden on `foo\bar` (Ctrl+Alt+L restores it). Original `locate.old.exe` unchanged. Prior delivery hashes and timings below are historical. + +## Fix — anchored path glob subtree planning, 2026-10-07 22:51 + +- Reproduced `C:\Windows\Logs\*.log`: previous native CLI median 2598.9 ms end-to-end (2581.8 ms service), versus legacy wrapper 152.8 ms, identical 62 paths. GUI-equivalent uncached name-sorted query took 3638.2 ms inside the service. Path glob terms were Unknown during name evaluation, so the engine reconstructed/matched paths across both complete volumes. +- Query planning now derives a conservative absolute directory prefix for every positive OR branch, folds/merges overlapping bounds, and falls back to general scanning if any branch is unbounded. `Volume` resolves those prefixes and walks child chains keyed by parent NTFS file reference. Case-colliding directory names are all considered; original predicates still enforce case, glob, metadata, AND/OR and negation. Only matching subtree rows are sorted for GUI name order. Existing wildcard semantics include nested descendants. +- Four-byte-per-row sibling links plus one hash head per parent are maintained on insertion, deletion, hardlink replacement and directory moves; directory renames retain descendant references. `finish()` rebuilds chains after snapshot restore/compaction. No disk-format change or full reindex is required. +- Expanded core tests from 86 to 442 checks: exhaustive-path oracle across glob/case/slash/Unicode/OR/negation/sorting variants, limits/cancellation, hardlinks and mutations, late parents, snapshot/compaction, cross-volume ordering and GUI pagination. A bounded-work regression includes 30000 unrelated names. All three CTest targets pass (442 core + 30 GUI cache + 16 icon cache). Live integration now compares both name and subtree queries and passed with 30 hardlinks, rename/move/delete, long Unicode paths and a 500-file create burst. +- After installing the service: exact-query native CLI median 14.6785 ms total, 0.486 ms service; legacy wrapper 160.0808 ms total. GUI-equivalent query with its completed-result cache cleared before each run: 0.571 ms service. All 62 paths match across old/native/GUI. Five measured runs after one warmup; index already resident. Scripts/data: `tests/path_benchmark.py`, `test-output/path-before.json`, `path-after.json`, `integration-path-subtree.log`, `ctest-path-subtree.log`. These figures exclude actual GUI input debounce/rendering and are not a general Everything-engine comparison. +- Program Files/PATH executables and release ZIP refreshed; deployment hashes recorded in `test-output/installation-path-subtree.json`. GUI restarted hidden on the user's path query to avoid taking keyboard focus; Ctrl+Alt+L restores it. Previous executable hashes below are historical. + +## Fix — actual file icons and full-path cache, 2026-10-07 + +- User observed that all three `Figma.exe` results stayed generic even when left idle. Confirmed in the installed UI. Previous async implementation only requested generic extension icons (`file.exe` with SHGFI_USEFILEATTRIBUTES); it never requested actual executable resources, so waiting could not change them. This was an incomplete follow-up load, not a missing repaint notification. +- `AsyncIcons` now has two layers: generic type icons provide the immediate fallback; the same dedicated COM worker loads actual file icons using the complete indexed path without SHGFI_USEFILEATTRIBUTES. Completion posts to the existing GUI handler and repaints automatically. File/path lookups remain off the GUI and search threads. +- Added a 4096-entry LRU cache keyed by full path, separate from type entries. Pending requests are deduplicated. Modification/creation timestamps, size and attributes invalidate a file entry; revision numbers reject outdated completions. Failed loads retain the generic fallback and retry after five seconds instead of poisoning the cache permanently or retrying every paint. +- Added 16 icon-cache checks covering fallback-to-file replacement, identical basename/extension in different directories, reuse, metadata invalidation, stale completion rejection, failure throttling/recovery, bounded eviction, and a real embedded EXE icon. Existing 30 GUI checks and 86 core checks also pass. Full UI integration/typing replay passed with a 250 ms maximum heartbeat gap on the 200 ms timer. +- Added `wlocate_ui_tests.exe --file-icons` for the user's exact query. All three Figma paths loaded actual icons (system image indices 50/51/52 in that test process) without further input. Render inspected: `test-output/ui-figma-icons/file-icons.png`; report: `test-output/ui-figma-icons/report.txt`. File-specific Figma logos are visible on every row. +- Rebuilt/installed Program Files and PATH copies and refreshed the release ZIP; hashes are in `test-output/installation-file-icons.json`. GUI restarted hidden on `figma.exe` with its tray/hotkey available to avoid taking keyboard focus. Earlier icon/installation notes below are historical. + +## Fix — input stalls with broad/intermediate queries, 2026-10-07 + +- User reported a several-second freeze while typing `python*`, before entering the final `n`. Isolated timings on the pre-fix controller: `pytho` service query ~84 ms and result publication ~7 ms; empty query ~0.93 s and `p` ~0.61 s on the worker. Those timings alone did not reproduce the reported UI stall (`test-output/gui-input-profile-before.txt`). +- Captured two installed-GUI stalls with CDB: the UI thread was inside `UIAutomationCore` bulk traversal of the stock owner-data ListView proxy. Intermediate `p` has ~1.61 million results, empty search ~3.27 million; the proxy walks every row and subitem. This also affected computer-use observation/input and could leave the application unresponsive for minutes. Stack captures: `gui-input-stack-before.txt`, `gui-input-stack-new.txt`. Do not attribute the user's original transient exclusively to icon loading; the directly observed long stall was UI Automation traversal. +- Added `gui_accessibility.hpp`: a server-side UIA fragment provider exposes visible rows only, with ItemContainer/VirtualizedItem for offscreen discovery and realization, native selection, focus, scroll-to-item and invoke. Row identities expire on query changes; detached providers cannot access destroyed windows. Calls use COM STA threading, keeping controls/cache access on the GUI thread. Native header accessibility remains available. A large select-all can be iterated through ItemContainer's IsSelected property; GetSelection avoids constructing arrays above 4096 elements. Reference: https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-workingwithvirtualizeditems +- Removed synchronous Shell icon lookup from startup/painting. `gui_icons.hpp` resolves generic type icons on a separate COM thread, deduplicates/cache requests, and posts completion to the GUI. It uses type names rather than probing indexed file paths. The worker owns detached shared state, so even a stuck Shell call cannot block search or window shutdown. Shared system image lists now use LVS_SHAREIMAGELISTS. Reference: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetfileinfow +- Text changes now immediately cancel/retire the old query; only launching the newest query is debounced. Late replies and missing-page requests cannot repaint results for previous text during the debounce interval. +- Expanded the existing cache regression target to 30 checks: controlled blocked icon provider, input/cancel/shutdown, visible-row accessibility over 3,000,000 rows, real UI Automation FindAll, offscreen realization/selection, and provider invalidation/lifetime. Real UIA traversal visited 16 visible rows in ~47 ms on five consecutive runs. Core tests (86 checks), CTest and UI integration pass. Typing replay `p -> py -> pyt -> pyth -> pytho -> python -> python*` had a maximum 204 ms heartbeat gap on a 200 ms timer. Reports: `test-output/gui-input-regression-final.txt`, `ui-input-uia-final/report.txt`. +- During development one test process exited with 0xC0000409; it did not recur under CDB, in five subsequent runs, CTest, or final GUI integration. The earlier same-process UIA test used Sleep polling and exposed costly cross-thread control calls; final implementation uses STA marshalling and the test pumps messages without polling delay. +- Updated Program Files/PATH binaries and the release ZIP. GUI restarted hidden on `python*` with its tray icon/hotkey available, because live desktop input was detected during verification. Current final hashes are recorded in `test-output/installation-input-fix.json` and the package SHA256SUMS; previous hashes below are historical. + +## Fix — repeated GUI queries after dismissing a context menu, 2026-10-07 + +- Reproduced the user's exact `figma.exe` scenario in the installed GUI with computer-use after its native pipe recovered: right-click the last of three results, dismiss onto blank list space, then move/click in the blank area. The row retained keyboard focus, and the status bar updated continuously. GUI CPU use was about 2.59 CPU seconds over a 3-second sample. +- Root cause: `receive()` removed the pending request and cleared page zero, then called `ListView_SetItemCountEx` before publishing the new page. The real Windows owner-data ListView synchronously requested the focused row (`LVN_GETDISPINFO`), found no cached data and queued page zero again. Each completion repeated the same cycle. Publish the page before notifying the control. Cache hints now reject negative/reversed/out-of-range intervals and clamp prefetch to the actual result count. +- Added `tests/gui_cache_tests.cpp` and the `gui_cache` CTest target. It exercises the production controller with actual common controls and deterministic replies, without a service or worker. The focused-last-row regression failed on the old code (one unwanted query), then passed after the fix. All 9 checks pass, including 100 refresh/repaint cycles without extra queries, preserved focus, empty results, bounded/deduplicated last-page fetches, and stale completions. Logs: `test-output/gui-cache-before.txt`, `gui-cache-after.txt`. +- Release build and both CTest targets pass (86 core checks plus 9 GUI cache checks). Existing UI integration passes search/filter grouping/paging/native Shell menus/Unicode rename; report and renders are in `test-output/ui-cache-fix`. +- Installed the corrected executable in Program Files and `D:\tools\bin`, restarted the service, and repeated the original mouse sequence in the installed GUI. Continuous refresh is gone; the normal two-second refresh remains. GUI CPU use fell to about 0.17 CPU seconds over the same 3-second sample (`test-output/gui-cache-cpu.json`). The window remains open on `figma.exe` for the user. `locate -n 10 "python*.exe"` still succeeds. +- WLocate SHA256: `30B925FF2F85719F2F046FC718F58DD8455B7749A957B2DD0009AC3D6E071848`; CLI unchanged. Release package regenerated with matching binaries. The earlier executable hashes below are historical. + +## Update — green icon and UTF-8 manifest, 2026-10-07 21:30 + +- User clarified the icon reference is Everything, not Evernote. Created original green magnifying-glass vector artwork with the same general orientation; no original icon pixels are used in the release. `assets/wlocate.svg` and `tools/Build-Icon.ps1` regenerate the nine-size (16–256 px) `assets/wlocate.ico`. +- Embedded the icon in both EXEs. Window, taskbar, tray, per-monitor DPI icon reload and Start-menu shortcut use it. UI screenshot verified at `test-output/ui-green-utf8/search.png`. +- Merged the `activeCodePage` UTF-8 setting from the user's `D:\work\dev3\utillib\utf8.manifest` into the existing shared manifest, preserving DPI, long paths, common controls and execution level. Both entry points enforce `GetACP() == 65001`; internal narrow strings/index/IPC remain UTF-8 with UTF-16 conversion at Windows W/Shell/UI boundaries. Compiler already uses `/utf-8`. +- Both actual EXE manifests extracted with mt.exe and checked. 86 core checks pass, including process ACP, implicit-code-page decoding and UTF-8 CreateFileA with Chinese/emoji. UI integration and live Unicode/long-path/hardlink integration pass. +- Reinstalled both Program Files and PATH copies, regenerated release ZIP, verified all deployed hashes match. wlocate SHA256: 44DBEE1555D0A1B7DD76C61C09DF6F104A5D1CFCFF2543A55AE8328C426BD1EE; locate: 0717B40DB2CEBFB598D4C9CE7DBBDF4079FE5C9877E1703595FE55EB6692E36E. Service Running; C:/D: ready, pending=0. GUI reopened for the user. + +The older delivery hashes and test counts below are historical. + +## Delivery status — 2026-10-07 21:10 (supersedes older notes below) + +- Both native executables built, tested, packaged and installed. `WLocateService` is Running/Auto from `C:\Program Files\WLocate\wlocate.exe --service`; both executables also installed in `D:\tools\bin`. Default `locate` now resolves to the native program, version 1.0.0. C: and D: report ready, pending=0, ~3.20 million records. +- Release: `out/WLocate-1.0.0-win-x64.zip`, unpacked beside it. README, performance report, installer, complete PCRE2/SLJIT/reference notices and SHA256SUMS included. Source and build/test scripts remain here; no commit or remote publication performed. +- Original Everything wrapper `D:\tools\bin\locate.old.exe` hash remains C9AB737C1F30183F954CD899F6B6A03380A00ABD4DDE72E832C66BB872959FA3. Pre-native C# wrapper backup: `D:\tools\bin\locate.exe.backup-20261007-210549`. `C:\Program Files\WLocate\install-state.json` preserves that original rollback path across native upgrades. +- Final executable hashes: wlocate 6E083E51C1BFD59D5CED5417570A4D71651CAE9BE68E0DB6AAEB03D72773BE35; locate 33E1D35F42B916E00B570006C102F7CAE53BF26F9E96C6D184FD77E46BF93F87. Build, Program Files, PATH and release copies match (`test-output/installation.json`). +- 83 core checks pass; live integration (Unicode, 30 long hardlinks, rename/move/delete/metadata, >300-character paths, 500-file create burst), protocol (60 concurrent requests, 20k rows, malformed requests, timeout recovery, admin checks), restart and VHD hotplug tests pass. UI self-test passed native menus across folders, Unicode Shell rename, filters, paging; rendered images inspected at `test-output/ui-final`. +- Stable same-output benchmark: python*.exe 66 identical paths, legacy median 68.86 ms vs native 9.10 ms. Native -n10 python 8.40 ms; -n10 *.exe 7.61 ms. See PERFORMANCE.md for measured broad-query limitations (uncached all-name sort 1.28 s, all-path 3.97 s, regex ~0.28 s; cached first pages ~0.2 ms). Do not claim universal Everything engine parity. +- Final critical correctness fix: NTFS header hardlink count includes separate DOS/8.3 FILE_NAME attributes. Track filename_attribute_count including DOS aliases while emitting only long names. Previous mismatch triggered permanent Access Denied fallback retries on protected files; after fix pending=0. New unit fixture verifies this, and 30-link live tests still pass. +- Snapshot v4 uses SHA-256 and atomic replacement. Keeps an earlier USN checkpoint for unresolved record retries. Name-only generation avoids metadata-only invalidation. Parent-path interning reduces sort allocations. PCRE2 10.49 JIT is statically linked into service. Buffered 64 KiB IPC, timeout/cancel and final acknowledgement protect output completeness. Compaction releases unused interned names too. +- Dynamic local NTFS drive manager polls every 2 s; independent indexers poll USN at 250 ms. One unavailable volume does not block ready ones; CLI marks partial results with stderr and exit 2. Protected Program Files and ProgramData directories; service repair always installs to Program Files. +- Unsupported: non-NTFS/network/folder indexes, no-letter volumes, content search, ETP/HTTP, all Everything advanced functions, exact GNU database compatibility. README documents support and limits. All-match initial sorts and memory footprint remain below Everything's efficiency. +- IDA analysis completed; Everything IDB saved at `..\reference\Everything-1.4.1.1032.exe.i64`, both IDA instances closed. Computer-use native pipe was unavailable; GUI validation used the application's own test target, not external mouse automation. See notices for IDA observations and references. +- psuac recipe: `Start-Process D:\tools\bin\psuac.exe -ArgumentList '-w powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File