using System.IO;
using System.Runtime.InteropServices;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;
namespace AxCopilot.Handlers;
///
/// Everything SDK를 이용한 초고속 파일 검색 핸들러.
/// "es" 프리픽스로 사용합니다.
///
/// 예: es report → "report" 파일명 검색
/// es *.xlsx → 엑셀 파일 검색
/// es 회의록 → 한글 파일명 검색
///
/// Everything이 설치되어 있지 않으면 자동으로 비활성화됩니다.
///
public class EverythingHandler : IActionHandler
{
public string? Prefix => "es";
public PluginMetadata Metadata => new(
"EverythingSearch",
"Everything 초고속 파일 검색 — es [키워드]",
"1.0",
"AX");
// ─── Everything SDK P/Invoke ─────────────────────────────────────────────
private const int EVERYTHING_OK = 0;
private const int EVERYTHING_REQUEST_FILE_NAME = 0x00000001;
private const int EVERYTHING_REQUEST_PATH = 0x00000002;
private const int EVERYTHING_REQUEST_SIZE = 0x00000010;
[DllImport("Everything64.dll", CharSet = CharSet.Unicode)]
private static extern uint Everything_SetSearchW(string lpSearchString);
[DllImport("Everything64.dll")]
private static extern void Everything_SetMax(uint dwMax);
[DllImport("Everything64.dll")]
private static extern void Everything_SetRequestFlags(uint dwRequestFlags);
[DllImport("Everything64.dll")]
private static extern bool Everything_QueryW(bool bWait);
[DllImport("Everything64.dll")]
private static extern uint Everything_GetNumResults();
[DllImport("Everything64.dll")]
private static extern uint Everything_GetLastError();
[DllImport("Everything64.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr Everything_GetResultFullPathNameW(uint nIndex, IntPtr lpString, uint nMaxCount);
[DllImport("Everything64.dll", CharSet = CharSet.Unicode)]
private static extern void Everything_GetResultSize(uint nIndex, out long lpFileSize);
[DllImport("Everything64.dll")]
private static extern uint Everything_GetMajorVersion();
// ─── 상태 ────────────────────────────────────────────────────────────────
private bool? _isAvailable;
private bool IsAvailable
{
get
{
if (_isAvailable.HasValue) return _isAvailable.Value;
try
{
Everything_GetMajorVersion();
_isAvailable = true;
}
catch
{
_isAvailable = false;
}
return _isAvailable.Value;
}
}
// ─── IActionHandler ──────────────────────────────────────────────────────
public Task> GetItemsAsync(string query, CancellationToken ct)
{
if (!IsAvailable)
{
return Task.FromResult>(new[]
{
new LauncherItem(
"Everything이 설치되어 있지 않습니다",
"voidtools.com에서 Everything을 설치하면 초고속 파일 검색을 사용할 수 있습니다",
null, null, Symbol: Symbols.Warning)
});
}
var q = query.Trim();
if (string.IsNullOrWhiteSpace(q))
{
return Task.FromResult>(new[]
{
new LauncherItem(
"Everything 파일 검색",
"검색어를 입력하세요 — 파일명, 확장자(*.xlsx), 경로 일부 등",
null, null, Symbol: Symbols.Search)
});
}
try
{
Everything_SetSearchW(q);
Everything_SetMax(30);
Everything_SetRequestFlags(EVERYTHING_REQUEST_FILE_NAME | EVERYTHING_REQUEST_PATH | EVERYTHING_REQUEST_SIZE);
if (!Everything_QueryW(true))
{
return Task.FromResult>(new[]
{
new LauncherItem(
"Everything 검색 실패",
$"오류 코드: {Everything_GetLastError()} — Everything 서비스가 실행 중인지 확인하세요",
null, null, Symbol: Symbols.Warning)
});
}
var count = Everything_GetNumResults();
if (count == 0)
{
return Task.FromResult>(new[]
{
new LauncherItem(
$"검색 결과 없음: {q}",
"다른 키워드나 와일드카드(*.xlsx)를 시도해 보세요",
null, null, Symbol: Symbols.Info)
});
}
var items = new List();
var buffer = Marshal.AllocHGlobal(520 * 2); // MAX_PATH * sizeof(wchar_t)
try
{
for (uint i = 0; i < count && i < 30; i++)
{
Everything_GetResultFullPathNameW(i, buffer, 520);
var fullPath = Marshal.PtrToStringUni(buffer) ?? "";
Everything_GetResultSize(i, out var fileSize);
var fileName = Path.GetFileName(fullPath);
var dirPath = Path.GetDirectoryName(fullPath) ?? "";
var sizeStr = fileSize > 0 ? FormatSize(fileSize) : "";
var subtitle = string.IsNullOrEmpty(sizeStr) ? dirPath : $"{sizeStr} · {dirPath}";
var symbol = Directory.Exists(fullPath) ? Symbols.Folder : GetFileSymbol(fullPath);
items.Add(new LauncherItem(fileName, subtitle, null, fullPath, Symbol: symbol));
}
}
finally
{
Marshal.FreeHGlobal(buffer);
}
return Task.FromResult>(items);
}
catch (Exception ex)
{
LogService.Warn($"Everything 검색 오류: {ex.Message}");
return Task.FromResult>(new[]
{
new LauncherItem("Everything 검색 오류", ex.Message, null, null, Symbol: Symbols.Warning)
});
}
}
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
{
if (item.Data is not string path || string.IsNullOrEmpty(path)) return;
try
{
if (Directory.Exists(path))
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = path,
UseShellExecute = true,
});
}
else if (File.Exists(path))
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = path,
UseShellExecute = true,
});
}
}
catch (Exception ex)
{
LogService.Warn($"Everything 결과 열기 실패: {ex.Message}");
}
await Task.CompletedTask;
}
// ─── 헬퍼 ────────────────────────────────────────────────────────────────
private static string FormatSize(long bytes)
{
if (bytes < 1024) return $"{bytes} B";
if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB";
if (bytes < 1024 * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB";
return $"{bytes / (1024.0 * 1024 * 1024):F2} GB";
}
private static string GetFileSymbol(string path)
{
var ext = Path.GetExtension(path).ToLowerInvariant();
return ext switch
{
".xlsx" or ".xls" or ".csv" => Symbols.Excel,
".docx" or ".doc" => Symbols.Word,
".pptx" or ".ppt" => Symbols.Slides,
".pdf" => Symbols.Pdf,
".png" or ".jpg" or ".jpeg" or ".gif" or ".bmp" or ".svg" => Symbols.Image,
".mp4" or ".avi" or ".mkv" or ".mov" => Symbols.Video,
".mp3" or ".wav" or ".flac" => Symbols.Music,
".zip" or ".rar" or ".7z" or ".tar" or ".gz" => Symbols.Archive,
".exe" or ".msi" => Symbols.App,
".cs" or ".py" or ".js" or ".ts" or ".java" or ".cpp" or ".c" or ".go" => Symbols.Code,
".json" or ".xml" or ".yaml" or ".yml" => Symbols.Config,
".txt" or ".md" or ".log" => Symbols.TextFile,
".html" or ".htm" or ".css" => Symbols.Web,
_ => Symbols.File,
};
}
}