190 lines
6.5 KiB
C#
190 lines
6.5 KiB
C#
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
using System.Windows;
|
|
using AxCopilot.SDK;
|
|
using AxCopilot.Services;
|
|
using AxCopilot.Themes;
|
|
|
|
namespace AxCopilot.Handlers;
|
|
|
|
/// <summary>
|
|
/// 파일 일괄 이름변경 핸들러. "rename" 프리픽스로 사용합니다.
|
|
/// 지정된 폴더 내 파일을 패턴으로 일괄 이름변경합니다.
|
|
/// 예: rename → 사용법 안내
|
|
/// rename C:\work\*.xlsx → 해당 폴더의 xlsx 파일 목록
|
|
/// rename C:\work\*.xlsx 보고서_{n} → 보고서_1.xlsx, 보고서_2.xlsx ...
|
|
/// {n}=순번, {date}=오늘 날짜, {orig}=원본명
|
|
/// Enter → 실행 전 미리보기, Shift+Enter → 실행.
|
|
/// </summary>
|
|
public class RenameHandler : IActionHandler
|
|
{
|
|
public string? Prefix => "rename";
|
|
|
|
public PluginMetadata Metadata => new(
|
|
"Rename",
|
|
"파일 일괄 이름변경 — rename",
|
|
"1.0",
|
|
"AX");
|
|
|
|
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
|
{
|
|
var q = query.Trim();
|
|
|
|
if (string.IsNullOrWhiteSpace(q))
|
|
{
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(
|
|
[
|
|
new LauncherItem(
|
|
"파일 일괄 이름변경",
|
|
"rename [폴더\\패턴] [새이름 템플릿]",
|
|
null, null, Symbol: Symbols.Rename),
|
|
new LauncherItem(
|
|
"사용 예시",
|
|
"rename C:\\work\\*.xlsx 보고서_{n} → 보고서_1.xlsx, 보고서_2.xlsx ...",
|
|
null, null, Symbol: Symbols.Info),
|
|
new LauncherItem(
|
|
"변수: {n} 순번, {date} 날짜, {orig} 원본명",
|
|
"rename D:\\photos\\*.jpg {date}_{n} → 2026-03-27_1.jpg ...",
|
|
null, null, Symbol: Symbols.Info),
|
|
]);
|
|
}
|
|
|
|
// 파싱: [경로\패턴] [템플릿]
|
|
var parts = q.Split(' ', 2, StringSplitOptions.TrimEntries);
|
|
var pattern = parts[0];
|
|
var template = parts.Length > 1 ? parts[1] : null;
|
|
|
|
// 경로 분리
|
|
var dir = Path.GetDirectoryName(pattern);
|
|
var glob = Path.GetFileName(pattern);
|
|
|
|
if (string.IsNullOrWhiteSpace(dir) || !Directory.Exists(dir))
|
|
{
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(
|
|
[
|
|
new LauncherItem(
|
|
"폴더를 찾을 수 없습니다",
|
|
$"경로: {dir ?? "(비어 있음)"}",
|
|
null, null, Symbol: Symbols.Warning)
|
|
]);
|
|
}
|
|
|
|
string[] files;
|
|
try { files = Directory.GetFiles(dir, glob); }
|
|
catch { files = Array.Empty<string>(); }
|
|
|
|
if (files.Length == 0)
|
|
{
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(
|
|
[
|
|
new LauncherItem(
|
|
"일치하는 파일이 없습니다",
|
|
$"패턴: {glob} · 폴더: {dir}",
|
|
null, null, Symbol: Symbols.Warning)
|
|
]);
|
|
}
|
|
|
|
Array.Sort(files);
|
|
|
|
// 템플릿이 없으면 파일 목록만 표시
|
|
if (string.IsNullOrWhiteSpace(template))
|
|
{
|
|
var items = files.Take(10).Select((f, i) => new LauncherItem(
|
|
Path.GetFileName(f),
|
|
dir,
|
|
null, null,
|
|
Symbol: Symbols.File)).ToList<LauncherItem>();
|
|
|
|
items.Insert(0, new LauncherItem(
|
|
$"총 {files.Length}개 파일 발견",
|
|
"뒤에 새 이름 템플릿을 추가하세요 (예: 보고서_{n})",
|
|
null, null, Symbol: Symbols.Info));
|
|
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
// 미리보기 생성
|
|
var today = DateTime.Today.ToString("yyyy-MM-dd");
|
|
var previews = new List<(string From, string To)>();
|
|
for (int i = 0; i < files.Length; i++)
|
|
{
|
|
var origName = Path.GetFileNameWithoutExtension(files[i]);
|
|
var ext = Path.GetExtension(files[i]);
|
|
var newName = template
|
|
.Replace("{n}", (i + 1).ToString())
|
|
.Replace("{date}", today)
|
|
.Replace("{orig}", origName);
|
|
// 확장자 자동 유지 (템플릿에 확장자가 없으면)
|
|
if (!Path.HasExtension(newName))
|
|
newName += ext;
|
|
previews.Add((Path.GetFileName(files[i]), newName));
|
|
}
|
|
|
|
var result = new List<LauncherItem>();
|
|
|
|
// 실행 항목
|
|
result.Add(new LauncherItem(
|
|
$"총 {files.Length}개 파일 이름변경 실행",
|
|
$"Enter로 실행 · {previews[0].From} → {previews[0].To} ...",
|
|
null, ValueTuple.Create(dir, files, previews.Select(p => p.To).ToArray()),
|
|
Symbol: Symbols.Rename));
|
|
|
|
// 미리보기 (최대 8개)
|
|
foreach (var (from, to) in previews.Take(8))
|
|
{
|
|
result.Add(new LauncherItem(
|
|
$"{from} → {to}",
|
|
"미리보기",
|
|
null, null,
|
|
Symbol: Symbols.File));
|
|
}
|
|
|
|
if (files.Length > 8)
|
|
{
|
|
result.Add(new LauncherItem(
|
|
$"... 외 {files.Length - 8}개",
|
|
"",
|
|
null, null,
|
|
Symbol: Symbols.Info));
|
|
}
|
|
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(result);
|
|
}
|
|
|
|
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
|
{
|
|
if (item.Data is not ValueTuple<string, string[], string[]> data)
|
|
return Task.CompletedTask;
|
|
|
|
var (dir, files, newNames) = data;
|
|
int renamed = 0;
|
|
int failed = 0;
|
|
|
|
for (int i = 0; i < files.Length && i < newNames.Length; i++)
|
|
{
|
|
try
|
|
{
|
|
var dest = Path.Combine(dir, newNames[i]);
|
|
if (File.Exists(dest))
|
|
{
|
|
failed++;
|
|
continue;
|
|
}
|
|
File.Move(files[i], dest);
|
|
renamed++;
|
|
}
|
|
catch
|
|
{
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
var msg = failed > 0
|
|
? $"{renamed}개 이름변경 완료, {failed}개 실패 (이미 존재하거나 접근 불가)"
|
|
: $"{renamed}개 파일 이름변경 완료";
|
|
NotificationService.Notify("AX Copilot", msg);
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|