설정창에서 런처 색인 진행률과 남은 시간을 직접 표시하도록 개선
Some checks failed
Release Gate / gate (push) Has been cancelled

- IndexService에 LauncherIndexProgressInfo와 진행 이벤트를 추가해 전체 색인 중 진행률, 상태 문구, 예상 남은 시간을 계산하도록 변경
- 설정창 일반 탭의 인덱싱 속도 아래에 프로그레스바와 상태/상세 문구 패널을 추가하고 실시간으로 바인딩되도록 연결
- 전체 색인 완료 후 최근 색인 완료 요약과 검색 가능 항목 수를 같은 위치에 유지해 런처 하단 완료 문구 의존도를 낮춤
- README와 DEVELOPMENT 문서에 변경 이력 및 반영 시각을 기록

검증
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\
- 경고 0 / 오류 0
This commit is contained in:
2026-04-06 19:43:18 +09:00
parent cc12177252
commit 1f52bc1cc3
5 changed files with 148 additions and 5 deletions

View File

@@ -48,9 +48,11 @@ public class IndexService : IDisposable
public IReadOnlyList<IndexEntry> Entries => _index;
public event EventHandler? IndexRebuilt;
public event EventHandler<LauncherIndexProgressInfo>? IndexProgressChanged;
public TimeSpan LastIndexDuration { get; private set; }
public int LastIndexCount { get; private set; }
public bool HasCachedIndexLoaded { get; private set; }
public LauncherIndexProgressInfo CurrentProgress { get; private set; } = LauncherIndexProgressInfo.Idle();
public IndexService(SettingsService settings)
{
@@ -110,16 +112,30 @@ public class IndexService : IDisposable
try
{
var fileSystemEntries = new List<IndexEntry>();
var paths = GetExpandedIndexPaths();
var paths = GetExpandedIndexPaths()
.Where(Directory.Exists)
.ToList();
var allowedExts = GetAllowedExtensions();
var indexSpeed = _settings.Settings.IndexSpeed ?? "normal";
foreach (var dir in paths)
ReportIndexProgress(LauncherIndexProgressInfo.Running(0, "색인 준비 중...", "남은 시간 계산 중"));
for (var index = 0; index < paths.Count; index++)
{
if (!Directory.Exists(dir))
continue;
var dir = paths[index];
var progressBase = paths.Count == 0 ? 0 : (double)index / paths.Count;
ReportIndexProgress(LauncherIndexProgressInfo.Running(
progressBase * 100,
$"스캔 중: {Path.GetFileName(dir)}",
EstimateRemainingText(progressBase, sw.Elapsed)));
await ScanDirectoryAsync(dir, fileSystemEntries, allowedExts, indexSpeed, ct);
var completedProgress = paths.Count == 0 ? 1 : (double)(index + 1) / paths.Count;
ReportIndexProgress(LauncherIndexProgressInfo.Running(
completedProgress * 100,
$"스캔 완료: {Path.GetFileName(dir)}",
EstimateRemainingText(completedProgress, sw.Elapsed)));
}
ComputeAllSearchCaches(fileSystemEntries);
@@ -135,6 +151,10 @@ public class IndexService : IDisposable
LastIndexCount = _index.Count;
HasCachedIndexLoaded = fileSystemEntries.Count > 0;
PersistCache();
ReportIndexProgress(LauncherIndexProgressInfo.Completed(
LastIndexCount,
LastIndexDuration,
$"최근 색인 완료 · {LastIndexCount:N0}개 항목 · {LastIndexDuration.TotalSeconds:F1}초"));
LogService.Info($"런처 인덱싱 완료: {fileSystemEntries.Count}개 파일 시스템 항목 ({sw.Elapsed.TotalSeconds:F1}초)");
IndexRebuilt?.Invoke(this, EventArgs.Empty);
}
@@ -291,10 +311,34 @@ public class IndexService : IDisposable
PersistCacheDeferred();
LastIndexCount = _index.Count;
IndexRebuilt?.Invoke(this, EventArgs.Empty);
CurrentProgress = LauncherIndexProgressInfo.Completed(
LastIndexCount,
LastIndexDuration,
$"최근 색인 완료 · {LastIndexCount:N0}개 항목");
LogService.Info($"런처 인덱스 증분 갱신: {triggerPath}");
}
private void ReportIndexProgress(LauncherIndexProgressInfo progress)
{
CurrentProgress = progress;
IndexProgressChanged?.Invoke(this, progress);
}
private static string EstimateRemainingText(double progressRatio, TimeSpan elapsed)
{
if (progressRatio <= 0.01)
return "남은 시간 계산 중";
var remaining = TimeSpan.FromMilliseconds(elapsed.TotalMilliseconds * ((1 - progressRatio) / progressRatio));
if (remaining.TotalSeconds < 1)
return "곧 완료";
if (remaining.TotalMinutes >= 1)
return $"예상 남은 시간 {remaining.Minutes + (remaining.Hours * 60)}분 {remaining.Seconds}초";
return $"예상 남은 시간 {Math.Max(1, (int)Math.Round(remaining.TotalSeconds))}초";
}
private bool AddPathEntryIncrementally(string fullPath, string rootPath)
{
if (ShouldIgnorePath(fullPath))
@@ -926,3 +970,37 @@ public enum IndexEntryType
Alias,
Folder
}
public sealed class LauncherIndexProgressInfo
{
public bool IsRunning { get; init; }
public double ProgressPercent { get; init; }
public string StatusText { get; init; } = "";
public string DetailText { get; init; } = "";
public static LauncherIndexProgressInfo Idle() => new()
{
IsRunning = false,
ProgressPercent = 0,
StatusText = "색인 대기 중",
DetailText = "필요할 때 전체 색인을 시작합니다."
};
public static LauncherIndexProgressInfo Running(double progressPercent, string statusText, string detailText) => new()
{
IsRunning = true,
ProgressPercent = Math.Clamp(progressPercent, 0, 100),
StatusText = statusText,
DetailText = detailText
};
public static LauncherIndexProgressInfo Completed(int count, TimeSpan duration, string statusText) => new()
{
IsRunning = false,
ProgressPercent = 100,
StatusText = statusText,
DetailText = count > 0
? $"현재 검색 가능 항목 {count:N0}개 · 최근 전체 색인 {duration.TotalSeconds:F1}초"
: "검색 가능한 항목이 없습니다."
};
}