Initial commit to new repository
This commit is contained in:
240
src/AxCopilot/Handlers/EverythingHandler.cs
Normal file
240
src/AxCopilot/Handlers/EverythingHandler.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Themes;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Everything SDK를 이용한 초고속 파일 검색 핸들러.
|
||||
/// "es" 프리픽스로 사용합니다.
|
||||
///
|
||||
/// 예: es report → "report" 파일명 검색
|
||||
/// es *.xlsx → 엑셀 파일 검색
|
||||
/// es 회의록 → 한글 파일명 검색
|
||||
///
|
||||
/// Everything이 설치되어 있지 않으면 자동으로 비활성화됩니다.
|
||||
/// </summary>
|
||||
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<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
if (!IsAvailable)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(new[]
|
||||
{
|
||||
new LauncherItem(
|
||||
"Everything이 설치되어 있지 않습니다",
|
||||
"voidtools.com에서 Everything을 설치하면 초고속 파일 검색을 사용할 수 있습니다",
|
||||
null, null, Symbol: Symbols.Warning)
|
||||
});
|
||||
}
|
||||
|
||||
var q = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(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<IEnumerable<LauncherItem>>(new[]
|
||||
{
|
||||
new LauncherItem(
|
||||
"Everything 검색 실패",
|
||||
$"오류 코드: {Everything_GetLastError()} — Everything 서비스가 실행 중인지 확인하세요",
|
||||
null, null, Symbol: Symbols.Warning)
|
||||
});
|
||||
}
|
||||
|
||||
var count = Everything_GetNumResults();
|
||||
if (count == 0)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(new[]
|
||||
{
|
||||
new LauncherItem(
|
||||
$"검색 결과 없음: {q}",
|
||||
"다른 키워드나 와일드카드(*.xlsx)를 시도해 보세요",
|
||||
null, null, Symbol: Symbols.Info)
|
||||
});
|
||||
}
|
||||
|
||||
var items = new List<LauncherItem>();
|
||||
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<IEnumerable<LauncherItem>>(items);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn($"Everything 검색 오류: {ex.Message}");
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user