Files

489 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AxCopilot.Core;
using AxCopilot.Models;
namespace AxCopilot.Services;
public class IndexService : IDisposable
{
private readonly SettingsService _settings;
private List<IndexEntry> _index = new List<IndexEntry>();
private readonly List<FileSystemWatcher> _watchers = new List<FileSystemWatcher>();
private Timer? _debounceTimer;
private readonly object _timerLock = new object();
private const int DebounceMs = 3000;
private readonly SemaphoreSlim _rebuildLock = new SemaphoreSlim(1, 1);
public IReadOnlyList<IndexEntry> Entries => _index;
public TimeSpan LastIndexDuration { get; private set; }
public int LastIndexCount { get; private set; }
public event EventHandler? IndexRebuilt;
public IndexService(SettingsService settings)
{
_settings = settings;
}
public async Task BuildAsync(CancellationToken ct = default(CancellationToken))
{
await _rebuildLock.WaitAsync(ct);
Stopwatch sw = Stopwatch.StartNew();
try
{
List<IndexEntry> entries = new List<IndexEntry>();
IEnumerable<string> paths = _settings.Settings.IndexPaths.Select((string p) => Environment.ExpandEnvironmentVariables(p));
HashSet<string> allowedExts = new HashSet<string>(_settings.Settings.IndexExtensions.Select((string e) => e.ToLowerInvariant().StartsWith(".") ? e.ToLowerInvariant() : ("." + e.ToLowerInvariant())), StringComparer.OrdinalIgnoreCase);
string indexSpeed = _settings.Settings.IndexSpeed ?? "normal";
foreach (string dir in paths)
{
if (Directory.Exists(dir))
{
await ScanDirectoryAsync(dir, entries, allowedExts, indexSpeed, ct);
}
}
foreach (AliasEntry alias in _settings.Settings.Aliases)
{
List<IndexEntry> list = entries;
IndexEntry indexEntry = new IndexEntry
{
Name = alias.Key,
DisplayName = (alias.Description ?? alias.Key),
Path = alias.Target,
AliasType = alias.Type
};
IndexEntry indexEntry2 = indexEntry;
string type = alias.Type;
if (1 == 0)
{
}
string text = type;
IndexEntryType type2 = ((!(text == "app")) ? ((!(text == "folder")) ? IndexEntryType.Alias : IndexEntryType.Folder) : IndexEntryType.App);
if (1 == 0)
{
}
indexEntry2.Type = type2;
indexEntry.Score = 100;
list.Add(indexEntry);
}
RegisterBuiltInApps(entries);
ComputeAllSearchCaches(entries);
_index = entries;
sw.Stop();
LastIndexDuration = sw.Elapsed;
LastIndexCount = entries.Count;
LogService.Info($"인덱싱 완료: {entries.Count}개 항목 ({sw.Elapsed.TotalSeconds:F1}초)");
this.IndexRebuilt?.Invoke(this, EventArgs.Empty);
}
finally
{
_rebuildLock.Release();
}
}
public void StartWatchers()
{
StopWatchers();
foreach (string indexPath in _settings.Settings.IndexPaths)
{
string text = Environment.ExpandEnvironmentVariables(indexPath);
if (Directory.Exists(text))
{
try
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(text)
{
Filter = "*.*",
IncludeSubdirectories = true,
NotifyFilter = (NotifyFilters.FileName | NotifyFilters.DirectoryName),
EnableRaisingEvents = true
};
fileSystemWatcher.Created += OnWatcherEvent;
fileSystemWatcher.Deleted += OnWatcherEvent;
fileSystemWatcher.Renamed += OnWatcherRenamed;
_watchers.Add(fileSystemWatcher);
}
catch (Exception ex)
{
LogService.Warn("FileSystemWatcher 생성 실패: " + text + " - " + ex.Message);
}
}
}
LogService.Info($"파일 감시 시작: {_watchers.Count}개 경로");
}
private void StopWatchers()
{
foreach (FileSystemWatcher watcher in _watchers)
{
watcher.EnableRaisingEvents = false;
watcher.Dispose();
}
_watchers.Clear();
}
private void OnWatcherEvent(object sender, FileSystemEventArgs e)
{
ScheduleRebuild(e.FullPath);
}
private void OnWatcherRenamed(object sender, RenamedEventArgs e)
{
ScheduleRebuild(e.FullPath);
}
private void ScheduleRebuild(string triggerPath)
{
LogService.Info($"파일 변경 감지: {triggerPath} — {3000}ms 후 재빌드 예약");
lock (_timerLock)
{
_debounceTimer?.Dispose();
_debounceTimer = new Timer(delegate
{
BuildAsync();
}, null, 3000, -1);
}
}
public void Dispose()
{
_debounceTimer?.Dispose();
StopWatchers();
}
private static void RegisterBuiltInApps(List<IndexEntry> entries)
{
(string, string[], string)[] array = new(string, string[], string)[43]
{
("메모장 (Notepad)", new string[4] { "메모장", "notepad", "note", "txt" }, "C:\\Windows\\notepad.exe"),
("계산기 (Calculator)", new string[3] { "계산기", "calc", "calculator" }, "calculator:"),
("캡처 도구 (Snipping Tool)", new string[5] { "캡처", "캡처도구", "snippingtool", "snip", "스크린샷" }, "C:\\Windows\\System32\\SnippingTool.exe"),
("그림판 (Paint)", new string[3] { "그림판", "mspaint", "paint" }, "C:\\Windows\\System32\\mspaint.exe"),
("파일 탐색기 (Explorer)", new string[3] { "탐색기", "explorer", "파일탐색기" }, "C:\\Windows\\explorer.exe"),
("명령 프롬프트 (CMD)", new string[4] { "cmd", "명령프롬프트", "커맨드", "터미널" }, "C:\\Windows\\System32\\cmd.exe"),
("PowerShell", new string[2] { "powershell", "파워쉘" }, "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"),
("제어판 (Control Panel)", new string[3] { "제어판", "control", "controlpanel" }, "C:\\Windows\\System32\\control.exe"),
("작업 관리자 (Task Manager)", new string[3] { "작업관리자", "taskmgr", "taskmanager" }, "C:\\Windows\\System32\\Taskmgr.exe"),
("원격 데스크톱 (Remote Desktop)", new string[4] { "원격", "mstsc", "rdp", "원격데스크톱" }, "C:\\Windows\\System32\\mstsc.exe"),
("레지스트리 편집기", new string[2] { "regedit", "레지스트리" }, "C:\\Windows\\regedit.exe"),
("장치 관리자", new string[3] { "장치관리자", "devmgmt", "devicemanager" }, "C:\\Windows\\System32\\devmgmt.msc"),
("Microsoft Edge", new string[3] { "edge", "엣지", "브라우저" }, "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"),
("Microsoft Excel", new string[6] { "엑셀", "excel", "msexcel", "xlsx", "xls", "csv" }, FindOfficeApp("EXCEL.EXE")),
("Microsoft PowerPoint", new string[5] { "파워포인트", "powerpoint", "mspowerpoint", "pptx", "ppt" }, FindOfficeApp("POWERPNT.EXE")),
("Microsoft Word", new string[5] { "워드", "word", "msword", "docx", "doc" }, FindOfficeApp("WINWORD.EXE")),
("Microsoft Outlook", new string[3] { "아웃룩", "outlook", "메일" }, FindOfficeApp("OUTLOOK.EXE")),
("Microsoft Teams", new string[2] { "팀즈", "teams" }, FindTeams()),
("Microsoft OneNote", new string[2] { "원노트", "onenote" }, FindOfficeApp("ONENOTE.EXE")),
("Microsoft Access", new string[3] { "액세스", "access", "msaccess" }, FindOfficeApp("MSACCESS.EXE")),
("Visual Studio Code", new string[4] { "vscode", "비주얼스튜디오코드", "코드에디터", "code" }, FindInPath("code.cmd") ?? FindInLocalAppData("Programs\\Microsoft VS Code\\Code.exe")),
("Visual Studio", new string[5] { "비주얼스튜디오", "devenv", "vs2022", "vs2019", "visualstudio" }, FindInProgramFiles("Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\devenv.exe") ?? FindInProgramFiles("Microsoft Visual Studio\\2022\\Professional\\Common7\\IDE\\devenv.exe") ?? FindInProgramFiles("Microsoft Visual Studio\\2022\\Enterprise\\Common7\\IDE\\devenv.exe")),
("Windows Terminal", new string[4] { "윈도우터미널", "wt", "windowsterminal", "터미널" }, FindInLocalAppData("Microsoft\\WindowsApps\\wt.exe")),
("OneDrive", new string[2] { "원드라이브", "onedrive" }, FindInLocalAppData("Microsoft\\OneDrive\\OneDrive.exe")),
("Google Chrome", new string[4] { "크롬", "구글크롬", "chrome", "google" }, FindInProgramFiles("Google\\Chrome\\Application\\chrome.exe") ?? FindInLocalAppData("Google\\Chrome\\Application\\chrome.exe")),
("Mozilla Firefox", new string[3] { "파이어폭스", "불여우", "firefox" }, FindInProgramFiles("Mozilla Firefox\\firefox.exe")),
("Naver Whale", new string[3] { "웨일", "네이버웨일", "whale" }, FindInLocalAppData("Naver\\Naver Whale\\Application\\whale.exe")),
("KakaoTalk", new string[4] { "카카오톡", "카톡", "kakaotalk", "kakao" }, FindInLocalAppData("Kakao\\KakaoTalk\\KakaoTalk.exe")),
("KakaoWork", new string[3] { "카카오워크", "카워크", "kakaowork" }, FindInLocalAppData("Kakao\\KakaoWork\\KakaoWork.exe")),
("Zoom", new string[2] { "줌", "zoom" }, FindInRoaming("Zoom\\bin\\Zoom.exe") ?? FindInLocalAppData("Zoom\\bin\\Zoom.exe")),
("Slack", new string[2] { "슬랙", "slack" }, FindInLocalAppData("slack\\slack.exe")),
("Figma", new string[2] { "피그마", "figma" }, FindInLocalAppData("Figma\\Figma.exe")),
("Notepad++", new string[3] { "노트패드++", "npp", "notepad++" }, FindInProgramFiles("Notepad++\\notepad++.exe")),
("7-Zip", new string[4] { "7zip", "7집", "세븐집", "7z" }, FindInProgramFiles("7-Zip\\7zFM.exe")),
("Bandizip", new string[2] { "반디집", "bandizip" }, FindInProgramFiles("Bandizip\\Bandizip.exe") ?? FindInLocalAppData("Bandizip\\Bandizip.exe")),
("ALZip", new string[3] { "알집", "alzip", "이스트소프트" }, FindInProgramFiles("ESTsoft\\ALZip\\ALZip.exe")),
("PotPlayer", new string[3] { "팟플레이어", "팟플", "potplayer" }, FindInProgramFiles("DAUM\\PotPlayer\\PotPlayerMini64.exe") ?? FindInProgramFiles("DAUM\\PotPlayer\\PotPlayerMini.exe")),
("GOM Player", new string[4] { "곰플레이어", "곰플", "gomplayer", "gom" }, FindInProgramFiles("GRETECH\\GomPlayer\\GOM.EXE")),
("Adobe Photoshop", new string[4] { "포토샵", "포샵", "photoshop", "ps" }, FindInProgramFiles("Adobe\\Adobe Photoshop 2025\\Photoshop.exe") ?? FindInProgramFiles("Adobe\\Adobe Photoshop 2024\\Photoshop.exe") ?? FindInProgramFiles("Adobe\\Adobe Photoshop 2023\\Photoshop.exe")),
("Adobe Acrobat", new string[3] { "아크로뱃", "acrobat", "아도비pdf" }, FindInProgramFiles("Adobe\\Acrobat DC\\Acrobat\\Acrobat.exe")),
("한컴 한글", new string[5] { "한글", "한컴한글", "hwp", "hangul", "아래아한글" }, FindInProgramFiles("HNC\\Hwp\\Hwp.exe") ?? FindInProgramFiles("HNC\\Office NEO\\HOffice NEO\\Bin\\Hwp.exe") ?? FindInProgramFiles("Hnc\\Hwp80\\Hwp.exe")),
("한컴 한셀", new string[3] { "한셀", "hcell", "한컴스프레드" }, FindInProgramFiles("HNC\\Hwp\\Hcell.exe") ?? FindInProgramFiles("HNC\\Office NEO\\HOffice NEO\\Bin\\Hcell.exe")),
("한컴 한쇼", new string[3] { "한쇼", "hshow", "한컴프레젠테이션" }, FindInProgramFiles("HNC\\Hwp\\Show.exe") ?? FindInProgramFiles("HNC\\Office NEO\\HOffice NEO\\Bin\\Show.exe"))
};
Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
Dictionary<string, int> dictionary2 = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < entries.Count; i++)
{
dictionary.TryAdd(entries[i].Name, i);
dictionary2.TryAdd(entries[i].Path, i);
}
(string, string[], string)[] array2 = array;
for (int j = 0; j < array2.Length; j++)
{
var (displayName, array3, text) = array2[j];
if (string.IsNullOrEmpty(text) || (!text.Contains(":") && !File.Exists(text) && !text.EndsWith(".msc")))
{
continue;
}
if (dictionary2.TryGetValue(text, out var value))
{
entries[value].DisplayName = displayName;
if (entries[value].Score < 95)
{
entries[value].Score = 95;
}
}
bool flag = dictionary2.ContainsKey(text);
string[] array4 = array3;
foreach (string text2 in array4)
{
if (dictionary.ContainsKey(text2))
{
if (dictionary.TryGetValue(text2, out var value2) && entries[value2].Score < 95)
{
entries[value2].Score = 95;
}
continue;
}
bool flag2 = text2.Any((char c) => c >= '가' && c <= '힣');
if (!flag || flag2)
{
entries.Add(new IndexEntry
{
Name = text2,
DisplayName = displayName,
Path = text,
Type = IndexEntryType.App,
Score = 95
});
int value3 = (dictionary[text2] = entries.Count - 1);
if (!flag)
{
dictionary2[text] = value3;
flag = true;
}
}
else
{
dictionary[text2] = dictionary2[text];
}
}
}
}
private static string? FindOfficeApp(string exeName)
{
string[] array = new string[4] { "C:\\Program Files\\Microsoft Office\\root\\Office16", "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16", "C:\\Program Files\\Microsoft Office\\Office16", "C:\\Program Files (x86)\\Microsoft Office\\Office16" };
string[] array2 = array;
foreach (string path in array2)
{
string text = Path.Combine(path, exeName);
if (File.Exists(text))
{
return text;
}
}
return null;
}
private static string? FindTeams()
{
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string text = Path.Combine(folderPath, "Microsoft\\Teams\\current\\Teams.exe");
if (File.Exists(text))
{
return text;
}
string text2 = Path.Combine(folderPath, "Microsoft\\WindowsApps\\ms-teams.exe");
return File.Exists(text2) ? text2 : null;
}
private static string? FindInPath(string fileName)
{
string text = Environment.GetEnvironmentVariable("PATH") ?? "";
string[] array = text.Split(';');
foreach (string text2 in array)
{
if (!string.IsNullOrWhiteSpace(text2))
{
string text3 = Path.Combine(text2.Trim(), fileName);
if (File.Exists(text3))
{
return text3;
}
}
}
return null;
}
private static string? FindInLocalAppData(string relativePath)
{
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string text = Path.Combine(folderPath, relativePath);
return File.Exists(text) ? text : null;
}
private static string? FindInProgramFiles(string relativePath)
{
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
string folderPath2 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
string text = Path.Combine(folderPath, relativePath);
if (File.Exists(text))
{
return text;
}
string text2 = Path.Combine(folderPath2, relativePath);
return File.Exists(text2) ? text2 : null;
}
private static string? FindInRoaming(string relativePath)
{
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string text = Path.Combine(folderPath, relativePath);
return File.Exists(text) ? text : null;
}
private static void ComputeAllSearchCaches(List<IndexEntry> entries)
{
foreach (IndexEntry entry in entries)
{
ComputeSearchCache(entry);
}
}
private static void ComputeSearchCache(IndexEntry entry)
{
entry.NameLower = entry.Name.ToLowerInvariant();
entry.NameJamo = FuzzyEngine.DecomposeToJamo(entry.NameLower);
StringBuilder stringBuilder = new StringBuilder(entry.NameLower.Length);
string nameLower = entry.NameLower;
foreach (char hangul in nameLower)
{
char chosung = FuzzyEngine.GetChosung(hangul);
if (chosung != 0)
{
stringBuilder.Append(chosung);
}
}
entry.NameChosung = stringBuilder.ToString();
}
private static (int batchSize, int delayMs) GetThrottle(string speed)
{
if (1 == 0)
{
}
(int, int) result = ((speed == "fast") ? (500, 0) : ((!(speed == "slow")) ? (150, 5) : (50, 15)));
if (1 == 0)
{
}
return result;
}
private static async Task ScanDirectoryAsync(string dir, List<IndexEntry> entries, HashSet<string> allowedExts, string indexSpeed, CancellationToken ct)
{
var (batchSize, delayMs) = GetThrottle(indexSpeed);
await Task.Run(async delegate
{
try
{
int count = 0;
foreach (string file in Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories))
{
ct.ThrowIfCancellationRequested();
string ext = Path.GetExtension(file).ToLowerInvariant();
if (allowedExts.Count <= 0 || allowedExts.Contains(ext))
{
string name = Path.GetFileNameWithoutExtension(file);
if (!string.IsNullOrEmpty(name))
{
if (1 == 0)
{
}
IndexEntryType indexEntryType;
switch (ext)
{
case ".exe":
indexEntryType = IndexEntryType.App;
break;
case ".lnk":
case ".url":
indexEntryType = IndexEntryType.File;
break;
default:
indexEntryType = IndexEntryType.File;
break;
}
if (1 == 0)
{
}
IndexEntryType type = indexEntryType;
List<IndexEntry> list = entries;
IndexEntry indexEntry = new IndexEntry
{
Name = name
};
IndexEntry indexEntry2 = indexEntry;
bool flag;
switch (ext)
{
case ".exe":
case ".lnk":
case ".url":
flag = true;
break;
default:
flag = false;
break;
}
indexEntry2.DisplayName = (flag ? name : (name + ext));
indexEntry.Path = file;
indexEntry.Type = type;
list.Add(indexEntry);
int num2;
if (delayMs > 0)
{
int num = count + 1;
count = num;
num2 = ((num % batchSize == 0) ? 1 : 0);
}
else
{
num2 = 0;
}
if (num2 != 0)
{
await Task.Delay(delayMs, ct);
}
}
}
}
foreach (string subDir in Directory.EnumerateDirectories(dir, "*", SearchOption.TopDirectoryOnly))
{
ct.ThrowIfCancellationRequested();
string name2 = Path.GetFileName(subDir);
if (!name2.StartsWith("."))
{
entries.Add(new IndexEntry
{
Name = name2,
DisplayName = name2,
Path = subDir,
Type = IndexEntryType.Folder
});
}
}
}
catch (UnauthorizedAccessException ex)
{
UnauthorizedAccessException ex2 = ex;
LogService.Warn("폴더 접근 불가 (건너뜀): " + dir + " - " + ex2.Message);
}
}, ct);
}
}