lsync 로 옮길 때 원본을 지우지 않음 안내 · 대상에만 있는 것을 사실대로 (243 · 244)
- 243 (사용자 편의, 기기 확인): 이동 카드를 lsync 로 옮기면 원본이 남아 헷갈렸다 (107: 실시간 동기화는 복사로 맞춤) - 확인 창에 알리고 쌍에 '복사로 맞춤' - 244 (버그, 기기 확인): 대상에만 있는 파일이 있는데 '같습니다' - 대상에만 있는 수를 세어 사실대로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>묶음5
parent
aa7a1af27c
commit
58bc8fa35b
|
|
@ -1000,6 +1000,7 @@
|
|||
"복사 실패 ({0}): {1}": "Copy failed ({0}): {1}",
|
||||
"복사 작업이라 칸의 원본 지우기 옵션 (--remove-source-files · /MOV) 은 쓰지 않습니다. 옮기려면 Rsync 화면에서 [원본 파일 지우기] 를 켜세요": "This is a copy task, so source-removing options in the field (--remove-source-files · /MOV) are not used. To move, turn on [Remove source files] on the Rsync screen",
|
||||
"복사(Ctrl+C)한 주소 자동 다운로드": "Auto-download copied (Ctrl+C) URLs",
|
||||
"복사로 맞춤": "Syncs by copying",
|
||||
"복사하는 중": "Copying",
|
||||
"복사할": "copy",
|
||||
"복사할 항목을 고르세요 (누르거나 오른쪽 동그라미로 표시).": "Choose items to copy (tap, or mark with the circle on the right).",
|
||||
|
|
@ -1191,6 +1192,7 @@
|
|||
"실시간 동기화: 원본 폴더": "Live sync: source folder",
|
||||
"실시간 동기화가 멈췄습니다": "Live sync stopped",
|
||||
"실시간 동기화가 없습니다. [추가] 하거나 복사 · rsync 탭에서 [lsync 로 이동] 하세요.": "No live sync. [Add] one, or use [Move to lsync] in the copy · rsync tab.",
|
||||
"실시간 동기화는 원본을 지우지 않습니다 (복사로 맞춤). 원본을 비우려면 lsync 의 [최종 정리] 를 쓰세요.": "Real-time sync never deletes the source (it syncs by copying). To empty the source, use [Final cleanup] in lsync.",
|
||||
"실시간 동기화를 지울까요?": "Delete this live sync?",
|
||||
"실시간 동기화의 [최종 정리]": "[Final cleanup] in live sync",
|
||||
"실제 실행: {0}": "Actually runs: {0}",
|
||||
|
|
@ -1385,6 +1387,7 @@
|
|||
"원본을 지우는 실행이라, 옮길 목록이 나온 뒤에 실행할 수 있습니다.": "This run removes source files, so it can run once the list of files to move is shown.",
|
||||
"원본을 지우는 작업입니다 (이동)": "This task removes source files (move)",
|
||||
"원본의 {0}배": "{0}× original",
|
||||
"원본의 것은 모두 대상에 있습니다 (대상에만 있는 것 {0}개는 그대로 둠)": "Everything in the source is in the target ({0} items only in the target are left as they are)",
|
||||
"원본의 빈 폴더 정리 중…": "Cleaning up empty folders in the source…",
|
||||
"원본의 파일을 모두 대상으로 옮기고 (원본에서 지움), 원본에 남은 빈 폴더를 지웁니다.": "Moves all files from the source to the target (deleting them from the source), then deletes the empty folders left in the source.",
|
||||
"원본이 대상 안에 있습니다 (환경 설정에서 막아 둠)": "The source is inside the target (blocked in settings)",
|
||||
|
|
|
|||
|
|
@ -1000,6 +1000,7 @@
|
|||
"복사 실패 ({0}): {1}": "コピー失敗 ({0}): {1}",
|
||||
"복사 작업이라 칸의 원본 지우기 옵션 (--remove-source-files · /MOV) 은 쓰지 않습니다. 옮기려면 Rsync 화면에서 [원본 파일 지우기] 를 켜세요": "コピー作業のため、欄の元ファイル削除オプション (--remove-source-files · /MOV) は使いません。移動するには Rsync 画面で [元ファイルを削除] をオンにしてください",
|
||||
"복사(Ctrl+C)한 주소 자동 다운로드": "コピー(Ctrl+C)した URL を自動ダウンロード",
|
||||
"복사로 맞춤": "コピーで合わせる",
|
||||
"복사하는 중": "コピー中",
|
||||
"복사할": "コピーする",
|
||||
"복사할 항목을 고르세요 (누르거나 오른쪽 동그라미로 표시).": "コピーする項目を選んでください (タップするか右の丸で印を付ける)。",
|
||||
|
|
@ -1191,6 +1192,7 @@
|
|||
"실시간 동기화: 원본 폴더": "リアルタイム同期: 元フォルダー",
|
||||
"실시간 동기화가 멈췄습니다": "リアルタイム同期が停止しました",
|
||||
"실시간 동기화가 없습니다. [추가] 하거나 복사 · rsync 탭에서 [lsync 로 이동] 하세요.": "リアルタイム同期はありません。[追加] するか、コピー · rsync タブで [lsync へ移動] してください。",
|
||||
"실시간 동기화는 원본을 지우지 않습니다 (복사로 맞춤). 원본을 비우려면 lsync 의 [최종 정리] 를 쓰세요.": "リアルタイム同期は元を削除しません (コピーで合わせます)。元を空にするには lsync の [最終整理] を使ってください。",
|
||||
"실시간 동기화를 지울까요?": "このリアルタイム同期を削除しますか?",
|
||||
"실시간 동기화의 [최종 정리]": "リアルタイム同期の [最終整理]",
|
||||
"실제 실행: {0}": "実際の実行: {0}",
|
||||
|
|
@ -1385,6 +1387,7 @@
|
|||
"원본을 지우는 실행이라, 옮길 목록이 나온 뒤에 실행할 수 있습니다.": "元のファイルを削除する実行のため、移動する一覧が表示されてから実行できます。",
|
||||
"원본을 지우는 작업입니다 (이동)": "元のファイルを削除する作業です (移動)",
|
||||
"원본의 {0}배": "元の {0}倍",
|
||||
"원본의 것은 모두 대상에 있습니다 (대상에만 있는 것 {0}개는 그대로 둠)": "元のものはすべて対象にあります (対象にだけある {0} 件はそのまま)",
|
||||
"원본의 빈 폴더 정리 중…": "元の空フォルダーを整理中…",
|
||||
"원본의 파일을 모두 대상으로 옮기고 (원본에서 지움), 원본에 남은 빈 폴더를 지웁니다.": "元のファイルをすべて対象へ移し (元から削除)、元に残った空フォルダーを削除します。",
|
||||
"원본이 대상 안에 있습니다 (환경 설정에서 막아 둠)": "元が対象の中にあります (環境設定で禁止)",
|
||||
|
|
|
|||
|
|
@ -1000,6 +1000,7 @@
|
|||
"복사 실패 ({0}): {1}": "复制失败 ({0}): {1}",
|
||||
"복사 작업이라 칸의 원본 지우기 옵션 (--remove-source-files · /MOV) 은 쓰지 않습니다. 옮기려면 Rsync 화면에서 [원본 파일 지우기] 를 켜세요": "这是复制任务,栏中的删除源文件选项(--remove-source-files · /MOV)不会使用。要移动,请在 Rsync 界面打开 [删除源文件]",
|
||||
"복사(Ctrl+C)한 주소 자동 다운로드": "自动下载复制(Ctrl+C)的地址",
|
||||
"복사로 맞춤": "以复制同步",
|
||||
"복사하는 중": "正在复制",
|
||||
"복사할": "复制",
|
||||
"복사할 항목을 고르세요 (누르거나 오른쪽 동그라미로 표시).": "请选择要复制的项目 (点按或用右侧圆圈标记)。",
|
||||
|
|
@ -1191,6 +1192,7 @@
|
|||
"실시간 동기화: 원본 폴더": "实时同步: 源文件夹",
|
||||
"실시간 동기화가 멈췄습니다": "实时同步已停止",
|
||||
"실시간 동기화가 없습니다. [추가] 하거나 복사 · rsync 탭에서 [lsync 로 이동] 하세요.": "没有实时同步。请 [添加],或在 复制 · rsync 标签中 [移到 lsync]。",
|
||||
"실시간 동기화는 원본을 지우지 않습니다 (복사로 맞춤). 원본을 비우려면 lsync 의 [최종 정리] 를 쓰세요.": "实时同步不会删除源(以复制方式同步)。要清空源,请使用 lsync 的 [最终整理]。",
|
||||
"실시간 동기화를 지울까요?": "删除此实时同步吗?",
|
||||
"실시간 동기화의 [최종 정리]": "实时同步的 [最终整理]",
|
||||
"실제 실행: {0}": "实际执行:{0}",
|
||||
|
|
@ -1385,6 +1387,7 @@
|
|||
"원본을 지우는 실행이라, 옮길 목록이 나온 뒤에 실행할 수 있습니다.": "此操作会删除源文件,需在显示要移动的列表后才能执行。",
|
||||
"원본을 지우는 작업입니다 (이동)": "此任务会删除源文件(移动)",
|
||||
"원본의 {0}배": "原始的 {0} 倍",
|
||||
"원본의 것은 모두 대상에 있습니다 (대상에만 있는 것 {0}개는 그대로 둠)": "源中的内容都已在目标中(仅在目标中的 {0} 项保持不变)",
|
||||
"원본의 빈 폴더 정리 중…": "正在清理源中的空文件夹…",
|
||||
"원본의 파일을 모두 대상으로 옮기고 (원본에서 지움), 원본에 남은 빈 폴더를 지웁니다.": "将源中的所有文件移到目标 (从源中删除),然后删除源中剩下的空文件夹。",
|
||||
"원본이 대상 안에 있습니다 (환경 설정에서 막아 둠)": "源位于目标内 (已在设置中阻止)",
|
||||
|
|
|
|||
|
|
@ -247,10 +247,28 @@ class LiveSync extends ChangeNotifier {
|
|||
}
|
||||
try {
|
||||
pending[k] = await diff(x);
|
||||
await _countOnlyTarget(x);
|
||||
} catch (_) {}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 244: 지우기 포함이 아닌 쌍에서 대상에만 있는 항목 수 (맞출 것이 없을 때 "같습니다" 대신 사실대로 보이려고)
|
||||
final onlyTarget = <String, int>{};
|
||||
|
||||
Future<void> _countOnlyTarget(LiveSyncPair x) async {
|
||||
final k = keyOf(x);
|
||||
if (x.delete || (pending[k]?.isNotEmpty ?? true)) {
|
||||
onlyTarget.remove(k);
|
||||
return;
|
||||
}
|
||||
// 지우기 포함으로 셌을 때 "지울 것" (− ) 이 곧 대상에만 있는 것
|
||||
try {
|
||||
onlyTarget[k] = [for (final d in await diff(x.copyWith(delete: true), limit: 10000)) if (d.startsWith('− ')) d].length;
|
||||
} catch (_) {
|
||||
onlyTarget.remove(k); // 세지 못하면 예전 글
|
||||
}
|
||||
}
|
||||
|
||||
static String keyOf(LiveSyncPair x) => '${x.source}=>${x.target}';
|
||||
|
||||
/// 지우기를 확인하지 않은 쌍: 대상에만 있어 지워질 항목 (원본 기준 상대 경로). 확인 창에 보여 준다 (42)
|
||||
|
|
@ -401,6 +419,7 @@ class LiveSync extends ChangeNotifier {
|
|||
problems.remove(k);
|
||||
emptyDeletes.remove(k);
|
||||
pending[k] = await diff(asked);
|
||||
await _countOnlyTarget(asked);
|
||||
if (n != 0) c.note(trf('실시간 동기화 ({0}): {1} → {2}', [method.label, x.source, x.target]));
|
||||
} on SourceUnreadableException catch (e) {
|
||||
// 68 · 70: 대상은 건드리지 않고 멈춤. 카드에 빨갛게 · 작업 알림에 · 로그에
|
||||
|
|
|
|||
|
|
@ -455,20 +455,30 @@ class _CopyCardState extends State<_CopyCard> {
|
|||
/// lsync 로: 끝나지 않은 것 (실행 중 · 실패 · 취소 · 아직 안 함) 도 옮길 수 있다 - 남은 것은 lsync 가 이어서 맞춘다.
|
||||
/// 실행 중이면 물어본 뒤 멈추고 옮긴다.
|
||||
Future<void> _toLive(CopyTask t) async {
|
||||
if (center.isRunning(t.id)) {
|
||||
final running = center.isRunning(t.id);
|
||||
// 243: 이동 카드 - 실시간 동기화는 원본을 지우지 않는다 (107). 옮기기 전에 알린다
|
||||
if (running || t.move) {
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
scrollable: true,
|
||||
scrollable: true,
|
||||
title: Text(tr('lsync 로 이동')),
|
||||
content: Text(tr('지금 실행 중입니다. 멈추고 lsync (실시간 동기화) 로 옮길까요? 남은 것은 lsync 가 이어서 맞춥니다.')),
|
||||
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
if (running) Text(tr('지금 실행 중입니다. 멈추고 lsync (실시간 동기화) 로 옮길까요? 남은 것은 lsync 가 이어서 맞춥니다.')),
|
||||
if (running && t.move) const SizedBox(height: 10),
|
||||
if (t.move)
|
||||
Text(tr('실시간 동기화는 원본을 지우지 않습니다 (복사로 맞춤). 원본을 비우려면 lsync 의 [최종 정리] 를 쓰세요.'),
|
||||
style: const TextStyle(color: Colors.orangeAccent)),
|
||||
]),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))),
|
||||
FilledButton(onPressed: () => Navigator.pop(ctx, true), child: Text(tr('멈추고 옮기기'))),
|
||||
FilledButton(onPressed: () => Navigator.pop(ctx, true), child: Text(running ? tr('멈추고 옮기기') : tr('옮기기'))),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (ok != true) return;
|
||||
if (ok != true || !mounted) return;
|
||||
}
|
||||
if (running) {
|
||||
final job = center.jobs[t.id]!;
|
||||
job.cancel();
|
||||
await job.done;
|
||||
|
|
@ -942,7 +952,8 @@ class _LiveTabState extends State<_LiveTab> {
|
|||
),
|
||||
Chip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
label: Text([tr(CopyMethod.of(x.method).label), if (x.delete) tr('지우기 포함')].join(' · ')),
|
||||
// 243: 실시간 동기화는 늘 복사로 맞춘다 (원본은 지우지 않음 - 원본을 비우려면 [최종 정리])
|
||||
label: Text([tr(CopyMethod.of(x.method).label), tr('복사로 맞춤'), if (x.delete) tr('지우기 포함')].join(' · ')),
|
||||
),
|
||||
// 이번 실행 동안 멈춤 / 시작 (설정의 켜기 · 끄기는 오른쪽 스위치)
|
||||
if (live != null && x.enabled)
|
||||
|
|
@ -1013,7 +1024,12 @@ class _LiveTabState extends State<_LiveTab> {
|
|||
child: pend == null
|
||||
? Text(tr('맞출 것을 세는 중…'), style: const TextStyle(fontSize: 12, color: JjColors.textDim))
|
||||
: pend.isEmpty
|
||||
? Text(tr('맞출 것 없음 (원본과 대상이 같습니다)'), style: const TextStyle(fontSize: 12, color: Colors.greenAccent))
|
||||
// 244: 대상에만 있는 것이 있으면 "같습니다" 가 아니라 사실대로
|
||||
? Text(
|
||||
(live?.onlyTarget[k] ?? 0) > 0
|
||||
? trf('원본의 것은 모두 대상에 있습니다 (대상에만 있는 것 {0}개는 그대로 둠)', [live!.onlyTarget[k]])
|
||||
: tr('맞출 것 없음 (원본과 대상이 같습니다)'),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.greenAccent))
|
||||
: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(
|
||||
trf('맞출 것 {0}개{1}', [pend.length >= 500 ? '500+' : pend.length, active ? '' : tr(' · 다음 시작 시간에 맞춥니다')]),
|
||||
|
|
|
|||
|
|
@ -277,6 +277,29 @@ void main() {
|
|||
expect(userErrorText('그냥 글'), '그냥 글');
|
||||
});
|
||||
|
||||
testWidgets('243: 이동 카드를 lsync 로 옮길 때 "원본을 지우지 않음 (복사로 맞춤)" 을 알리고, 취소하면 옮기지 않는다', (tester) async {
|
||||
final src = p.join(tmp.path, 'mv243'), dst = p.join(tmp.path, 'mv243d');
|
||||
Directory(src).createSync();
|
||||
Directory(dst).createSync();
|
||||
c.settings.copyTasks = [CopyTask(id: '1', sources: [src], dest: dst, contents: true, move: true, method: 'builtin')];
|
||||
tester.view.physicalSize = const Size(1500, 1000);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
await tester.pumpWidget(MaterialApp(home: MonitorPage(c: c)));
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('lsync 로 이동').first);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('실시간 동기화는 원본을 지우지 않습니다 (복사로 맞춤)'), findsOneWidget);
|
||||
await tester.tap(find.text('취소').last);
|
||||
await tester.pumpAndSettle();
|
||||
expect(c.settings.liveSyncPairs, isEmpty, reason: '취소하면 옮기지 않음');
|
||||
await tester.tap(find.text('lsync 로 이동').first);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.widgetWithText(FilledButton, '옮기기'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(c.settings.liveSyncPairs.map((x) => x.source), [src]);
|
||||
});
|
||||
|
||||
testWidgets('105: 원본이 대상 안 + 창에서 [원본 파일 지우기] 로 지우기 옵션이 생기면 막는다', (tester) async {
|
||||
final outer = p.join(tmp.path, 'O'), inner = p.join(outer, 'in');
|
||||
File(p.join(inner, 'n.txt'))
|
||||
|
|
|
|||
|
|
@ -237,6 +237,31 @@ void main() {
|
|||
expect(live.watching.map((x) => x.source), [b.path]);
|
||||
});
|
||||
|
||||
test('244: 맞출 것이 없어도 대상에만 있는 것이 있으면 "같습니다" 가 아니라 그 수를 센다', () async {
|
||||
final tmp = Directory.systemTemp.createTempSync('jj_only_');
|
||||
addTearDown(() => tmp.deleteSync(recursive: true));
|
||||
final a = Directory(p.join(tmp.path, 'a'))..createSync();
|
||||
final t = Directory(p.join(tmp.path, 't'))..createSync();
|
||||
final now = DateTime.now();
|
||||
for (final d in [a, t]) {
|
||||
(File(p.join(d.path, 'same.txt'))..writeAsStringSync('s')).setLastModifiedSync(now);
|
||||
}
|
||||
File(p.join(t.path, 'only1.txt')).writeAsStringSync('1');
|
||||
Directory(p.join(t.path, 'onlydir')).createSync();
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
final pair = LiveSyncPair(a.path, t.path, method: 'builtin');
|
||||
c.settings.liveSyncPairs = [pair];
|
||||
final live = LiveSync(c);
|
||||
addTearDown(live.dispose);
|
||||
await live.refreshPending(pair);
|
||||
expect(live.pending[LiveSync.keyOf(pair)], isEmpty);
|
||||
expect(live.onlyTarget[LiveSync.keyOf(pair)], 2);
|
||||
File(p.join(t.path, 'only1.txt')).deleteSync();
|
||||
Directory(p.join(t.path, 'onlydir')).deleteSync();
|
||||
await live.refreshPending(pair);
|
||||
expect(live.onlyTarget[LiveSync.keyOf(pair)], 0, reason: '정말 같으면 0 - "같습니다"');
|
||||
});
|
||||
|
||||
test('dispose 뒤에 맞추기가 끝나도 알리지 않는다 (앱을 끄는 중 - used after disposed 오류 없음)', () async {
|
||||
final tmp = Directory.systemTemp.createTempSync('jj_disp_');
|
||||
addTearDown(() => tmp.deleteSync(recursive: true));
|
||||
|
|
|
|||
Loading…
Reference in New Issue