489 lines
18 KiB
C#
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);
|
|
}
|
|
}
|