앱 직접 복사 · 이동은 받는 쪽이 더 새것이면 건너뜀 · 미리 보기 경고 (229)
- 데이터 (버그): 현재 방식 · WebDAV 의 이동이 받는 쪽의 더 새 파일을 예전 원본으로 덮어쓰고 원본까지 지웠다 - 환경 설정 '받는 쪽이 더 새것이면 건너뛰기' (copySkipNewer, 처음 값 켜짐 - 관리자 판단). rsync -u 와 같음 - 건너뛴 파일은 이동이라도 원본에 남기고 'n개는 원본에 남음' 으로 알림 - 미리 보기: 건너뛰는 것 (받는 쪽이 더 새것 - 건너뜀), 더 새 파일을 덮어쓸 때 주황 굵은 경고 - 시험 sync_preview_test 기대값: -u 일 때 leftold.txt 는 목록에서 빠지지 않고 skipNewer 로 보임 (관리자 승인) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>fix_mkv
parent
27d50891d0
commit
c070486c84
|
|
@ -46,9 +46,11 @@
|
|||
"'사진' 모델로 바꿔 다시": "Switch to 'Photos' and redo",
|
||||
"(AI 번역을 쓸 수 없는 기기라 언어를 더할 수 없습니다)": "(Languages cannot be added because AI translation is unavailable on this device)",
|
||||
"({0}개 지움)": "(delete {0})",
|
||||
"(더 새 파일을 덮어씀)": "(overwrites a newer file)",
|
||||
"(덮어씀)": "(overwritten)",
|
||||
"(동영상을 세 번 누르면 옮깁니다)": "(Tapping a video three times moves it)",
|
||||
"(받는 쪽에만 있음)": "(only on receiving side)",
|
||||
"(받는 쪽이 더 새것 - 건너뜀)": "(receiving side is newer - skipped)",
|
||||
"(비어 있음)": "(empty)",
|
||||
"(설명 없음)": "(no description)",
|
||||
"(지워짐)": "(deleted)",
|
||||
|
|
@ -518,6 +520,7 @@
|
|||
"그림을 읽을 수 없습니다": "Cannot read the image",
|
||||
"그림을 페이지로 넣기 (이 페이지 뒤)": "Add images as pages (after this page)",
|
||||
"그림이 거의 같고 마지막 단계가 10배쯤 빠릅니다 (받아 두었을 때)": "Almost the same image, and the last step is about 10× faster (when downloaded)",
|
||||
"그중 {0}개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다": "{0} of them are newer on the receiving side - the newer files will be overwritten with older ones",
|
||||
"글 → 그림 · 그림 → 그림 (기기 안에서, 모델은 환경 설정에서 받기)": "Text → image · image → image (on this device; download models in Settings)",
|
||||
"글을 격자에 반영": "Apply text to grid",
|
||||
"글자 · 버튼 크기입니다. 위쪽 막대의 − · + 로 그때그때 바꿀 수 있고, 가운데 숫자를 누르면 이 크기로 돌아갑니다 (지금 {0}%)": "Text · button size. Change it any time with − · + on the top bar; press the number in the middle to return to this size (now {0}%)",
|
||||
|
|
@ -908,6 +911,8 @@
|
|||
"받는 쪽에만 있음 {0} (그대로 둠)": "Only on receiving side {0} (kept)",
|
||||
"받는 쪽에서 {0}개가 지워집니다 (--delete, 되돌릴 수 없음)": "{0} items will be deleted on the receiving side (--delete, cannot be undone)",
|
||||
"받는 쪽에서 {0}개를 덮어씁니다 (내용이 다른 같은 이름 - 원래 것은 없어짐)": "{0} files on the receiving side will be overwritten (same name, different content - the original is lost)",
|
||||
"받는 쪽이 더 새것 {0} (건너뜀)": "Newer on receiving side {0} (skipped)",
|
||||
"받는 쪽이 더 새것이면 건너뛰기": "Skip when the receiving side is newer",
|
||||
"받는 크기: 약 {0}MB · 받은 파일 · AI 모델 · 설정은 그대로 둡니다": "Download size: about {0}MB · downloaded files · AI models · settings are kept",
|
||||
"받다 만 AI 모델": "Unfinished AI models",
|
||||
"받다 만 다운로드": "Unfinished downloads",
|
||||
|
|
@ -2029,6 +2034,7 @@
|
|||
"허용했으면 다시 확인": "Check again after allowing",
|
||||
"헝가리어": "Hungarian",
|
||||
"현재 방식": "Built-in",
|
||||
"현재 방식 · WebDAV 로 폴더 안의 것을 맞출 때 (rsync 의 -u 와 같음). 건너뛴 파일은 이동이라도 원본에 남깁니다": "When syncing folder contents with the built-in method or WebDAV (same as rsync -u). Skipped files stay in the source even when moving",
|
||||
"현재 유지": "Keep as is",
|
||||
"현재 페이지": "Current page",
|
||||
"현재 페이지 추가": "Add current page",
|
||||
|
|
|
|||
|
|
@ -46,9 +46,11 @@
|
|||
"'사진' 모델로 바꿔 다시": "「写真」モデルに変えてやり直す",
|
||||
"(AI 번역을 쓸 수 없는 기기라 언어를 더할 수 없습니다)": "(この端末では AI 翻訳が使えないため言語を追加できません)",
|
||||
"({0}개 지움)": "({0} 個を削除)",
|
||||
"(더 새 파일을 덮어씀)": "(新しいファイルを上書き)",
|
||||
"(덮어씀)": "(上書き)",
|
||||
"(동영상을 세 번 누르면 옮깁니다)": "(動画を 3 回タップすると移動します)",
|
||||
"(받는 쪽에만 있음)": "(受け取る側にだけある)",
|
||||
"(받는 쪽이 더 새것 - 건너뜀)": "(受け取る側が新しい - スキップ)",
|
||||
"(비어 있음)": "(空)",
|
||||
"(설명 없음)": "(説明なし)",
|
||||
"(지워짐)": "(削除)",
|
||||
|
|
@ -518,6 +520,7 @@
|
|||
"그림을 읽을 수 없습니다": "画像を読み込めません",
|
||||
"그림을 페이지로 넣기 (이 페이지 뒤)": "画像をページとして追加 (このページの後)",
|
||||
"그림이 거의 같고 마지막 단계가 10배쯤 빠릅니다 (받아 두었을 때)": "画像はほぼ同じで、最後の段階が約 10 倍速くなります (ダウンロード済みの場合)",
|
||||
"그중 {0}개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다": "そのうち {0} 件は受け取る側の方が新しいです - 新しいファイルを古いもので上書きします",
|
||||
"글 → 그림 · 그림 → 그림 (기기 안에서, 모델은 환경 설정에서 받기)": "テキスト → 画像 · 画像 → 画像 (端末内で。モデルは設定でダウンロード)",
|
||||
"글을 격자에 반영": "テキストをグリッドに反映",
|
||||
"글자 · 버튼 크기입니다. 위쪽 막대의 − · + 로 그때그때 바꿀 수 있고, 가운데 숫자를 누르면 이 크기로 돌아갑니다 (지금 {0}%)": "文字・ボタンの大きさです。上部バーの − ・ + でその都度変更でき、中央の数字を押すとこの大きさに戻ります(現在 {0}%)",
|
||||
|
|
@ -908,6 +911,8 @@
|
|||
"받는 쪽에만 있음 {0} (그대로 둠)": "受け取る側にだけある {0} (そのまま)",
|
||||
"받는 쪽에서 {0}개가 지워집니다 (--delete, 되돌릴 수 없음)": "受け取る側で {0} 個が削除されます (--delete、元に戻せません)",
|
||||
"받는 쪽에서 {0}개를 덮어씁니다 (내용이 다른 같은 이름 - 원래 것은 없어짐)": "受け取る側の {0} 件を上書きします (同じ名前で内容が違うもの - 元のものはなくなります)",
|
||||
"받는 쪽이 더 새것 {0} (건너뜀)": "受け取る側が新しい {0} (スキップ)",
|
||||
"받는 쪽이 더 새것이면 건너뛰기": "受け取る側が新しければスキップ",
|
||||
"받는 크기: 약 {0}MB · 받은 파일 · AI 모델 · 설정은 그대로 둡니다": "ダウンロードサイズ: 約 {0}MB・ダウンロードしたファイル・AI モデル・設定はそのまま",
|
||||
"받다 만 AI 모델": "ダウンロード途中の AI モデル",
|
||||
"받다 만 다운로드": "ダウンロード途中のファイル",
|
||||
|
|
@ -2029,6 +2034,7 @@
|
|||
"허용했으면 다시 확인": "許可したらもう一度確認",
|
||||
"헝가리어": "ハンガリー語",
|
||||
"현재 방식": "現在の方式",
|
||||
"현재 방식 · WebDAV 로 폴더 안의 것을 맞출 때 (rsync 의 -u 와 같음). 건너뛴 파일은 이동이라도 원본에 남깁니다": "現在の方式 · WebDAV でフォルダーの中身を合わせるとき (rsync の -u と同じ)。スキップしたファイルは移動でも元の場所に残します",
|
||||
"현재 유지": "現状維持",
|
||||
"현재 페이지": "現在のページ",
|
||||
"현재 페이지 추가": "現在のページを追加",
|
||||
|
|
|
|||
|
|
@ -46,9 +46,11 @@
|
|||
"'사진' 모델로 바꿔 다시": "改用“照片”模型重新处理",
|
||||
"(AI 번역을 쓸 수 없는 기기라 언어를 더할 수 없습니다)": "(此设备无法使用 AI 翻译,因此无法添加语言)",
|
||||
"({0}개 지움)": "(删除 {0} 个)",
|
||||
"(더 새 파일을 덮어씀)": "(覆盖较新的文件)",
|
||||
"(덮어씀)": "(覆盖)",
|
||||
"(동영상을 세 번 누르면 옮깁니다)": "(连按视频三次会移动它)",
|
||||
"(받는 쪽에만 있음)": "(仅在接收方)",
|
||||
"(받는 쪽이 더 새것 - 건너뜀)": "(接收端较新 - 已跳过)",
|
||||
"(비어 있음)": "(空)",
|
||||
"(설명 없음)": "(无说明)",
|
||||
"(지워짐)": "(将删除)",
|
||||
|
|
@ -518,6 +520,7 @@
|
|||
"그림을 읽을 수 없습니다": "无法读取图片",
|
||||
"그림을 페이지로 넣기 (이 페이지 뒤)": "将图片作为页面加入 (本页之后)",
|
||||
"그림이 거의 같고 마지막 단계가 10배쯤 빠릅니다 (받아 두었을 때)": "图片几乎相同,最后一步快约 10 倍 (已下载时)",
|
||||
"그중 {0}개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다": "其中 {0} 个在接收端较新 - 将用旧文件覆盖较新的文件",
|
||||
"글 → 그림 · 그림 → 그림 (기기 안에서, 모델은 환경 설정에서 받기)": "文字 → 图片 · 图片 → 图片(在本设备上;模型在设置中下载)",
|
||||
"글을 격자에 반영": "将文本应用到网格",
|
||||
"글자 · 버튼 크기입니다. 위쪽 막대의 − · + 로 그때그때 바꿀 수 있고, 가운데 숫자를 누르면 이 크기로 돌아갑니다 (지금 {0}%)": "文字 · 按钮大小。可随时用顶部栏的 − · + 调整,点击中间的数字恢复为此大小(当前 {0}%)",
|
||||
|
|
@ -908,6 +911,8 @@
|
|||
"받는 쪽에만 있음 {0} (그대로 둠)": "仅在接收方 {0} (保留)",
|
||||
"받는 쪽에서 {0}개가 지워집니다 (--delete, 되돌릴 수 없음)": "接收方将删除 {0} 个项目 (--delete,无法撤销)",
|
||||
"받는 쪽에서 {0}개를 덮어씁니다 (내용이 다른 같은 이름 - 원래 것은 없어짐)": "将覆盖接收端的 {0} 个文件(同名但内容不同 - 原文件将丢失)",
|
||||
"받는 쪽이 더 새것 {0} (건너뜀)": "接收端较新 {0}(跳过)",
|
||||
"받는 쪽이 더 새것이면 건너뛰기": "接收端较新时跳过",
|
||||
"받는 크기: 약 {0}MB · 받은 파일 · AI 모델 · 설정은 그대로 둡니다": "下载大小: 约 {0}MB · 已下载的文件 · AI 模型 · 设置均保留",
|
||||
"받다 만 AI 모델": "未下载完的 AI 模型",
|
||||
"받다 만 다운로드": "未完成的下载",
|
||||
|
|
@ -2029,6 +2034,7 @@
|
|||
"허용했으면 다시 확인": "允许后重新检查",
|
||||
"헝가리어": "匈牙利语",
|
||||
"현재 방식": "当前方式",
|
||||
"현재 방식 · WebDAV 로 폴더 안의 것을 맞출 때 (rsync 의 -u 와 같음). 건너뛴 파일은 이동이라도 원본에 남깁니다": "使用当前方式 · WebDAV 同步文件夹内容时(与 rsync 的 -u 相同)。跳过的文件即使是移动也会保留在源中",
|
||||
"현재 유지": "保持现状",
|
||||
"현재 페이지": "当前页面",
|
||||
"현재 페이지 추가": "添加当前页面",
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ class CopyCenter extends ChangeNotifier {
|
|||
contents: t.contents,
|
||||
prune: t.move ? t.prune : '',
|
||||
conflict: conflict,
|
||||
skipNewer: c.settings.copySkipNewer,
|
||||
);
|
||||
jobs[t.id] = job;
|
||||
job.addListener(notifyListeners);
|
||||
|
|
|
|||
|
|
@ -373,6 +373,10 @@ class AppSettings {
|
|||
|
||||
/// 48: 복사 · 이동할 때 받는 폴더에 같은 이름이 있으면 'ask' (확인 창에서 고름, 처음 값) · 'rename' · 'overwrite' · 'skip'
|
||||
String copyConflict = 'ask';
|
||||
|
||||
/// 229: 앱이 직접 하는 복사 · 이동 (현재 방식 · WebDAV 의 안의 것 맞추기) 에서 받는 쪽이 더 새것이면 건너뛴다 (rsync -u 와 같음).
|
||||
/// 처음 값 켜짐 - 예전 원본으로 새 파일을 덮어쓰고 (이동이면 원본까지 지워) 새 내용을 잃었다 (관리자 판단)
|
||||
bool copySkipNewer = true;
|
||||
String copyMethodFolder = 'builtin';
|
||||
String rsyncOptions = '-avPog';
|
||||
String robocopyOptions = '/E /COPY:DAT /DCOPY:T /R:2 /W:2';
|
||||
|
|
@ -667,6 +671,7 @@ class AppSettings {
|
|||
'explorerPaths': explorerPaths,
|
||||
'copyMethodFile': copyMethodFile,
|
||||
'copyConflict': copyConflict,
|
||||
'copySkipNewer': copySkipNewer,
|
||||
'copyMethodFolder': copyMethodFolder,
|
||||
'rsyncPaths': rsyncPaths,
|
||||
'components': components,
|
||||
|
|
@ -853,6 +858,7 @@ class AppSettings {
|
|||
..explorerPaths = [for (final x in (j['explorerPaths'] as List?) ?? const []) '$x']
|
||||
..copyMethodFile = _method(j['copyMethodFile'])
|
||||
..copyConflict = const ['rename', 'overwrite', 'skip'].contains(j['copyConflict']) ? j['copyConflict'] as String : 'ask'
|
||||
..copySkipNewer = j['copySkipNewer'] != false
|
||||
..copyMethodFolder = _method(j['copyMethodFolder'])
|
||||
..rsyncPaths = [for (final x in (j['rsyncPaths'] as List?) ?? const []) '$x']
|
||||
..components = j['components'] is List
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@ class TransferJob extends ChangeNotifier {
|
|||
/// 48: 같은 이름이 있을 때 (앱이 직접 복사 · 이동할 때만 - robocopy · rsync 는 그 프로그램의 규칙)
|
||||
final NameConflict conflict;
|
||||
|
||||
/// 229: 앱이 직접 맞출 때 받는 쪽이 더 새것이면 건너뛴다 (환경 설정 - 옵션 칸이 없는 현재 방식 · WebDAV 도). 건너뛴 원본은 이동이라도 남긴다
|
||||
final bool skipNewer;
|
||||
|
||||
/// 같은 이름이라 건너뛴 항목 (원본 경로)
|
||||
final skipped = <String>[];
|
||||
|
||||
|
|
@ -77,6 +80,7 @@ class TransferJob extends ChangeNotifier {
|
|||
this.contents = false,
|
||||
this.prune = '',
|
||||
this.conflict = NameConflict.rename,
|
||||
this.skipNewer = false,
|
||||
});
|
||||
|
||||
/// 지금 복사하는 파일 (원본 기준 상대 경로, rsync 는 이름을 먼저 알리고 그 파일의 진행률을 알린다)
|
||||
|
|
@ -290,7 +294,7 @@ class TransferJob extends ChangeNotifier {
|
|||
if (contents && (isDav(sources[i]) || FileSystemEntity.isDirectorySync(sources[i]))) {
|
||||
// 폴더 "안의 것" 맞추기 (rsync 원본/ 대상/ 과 같은 뜻): -u · --delete · 원본 파일 지우기 (--remove-source-files) 도
|
||||
settled.clear();
|
||||
await _ops!.mirror(sources[i], dest, update: _update, delete: _delete, strict: move);
|
||||
await _ops!.mirror(sources[i], dest, update: _update || skipNewer, delete: _delete, strict: move);
|
||||
if (move && !cancelled) await _deleteSettled();
|
||||
made.add(dest);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ enum PreviewAction {
|
|||
|
||||
/// 받는 쪽에만 있어 지워짐 (--delete)
|
||||
delete,
|
||||
|
||||
/// 229: 받는 쪽이 더 새것이라 건너뜀 (-u · 환경 설정 "받는 쪽이 더 새것이면 건너뛰기")
|
||||
skipNewer,
|
||||
}
|
||||
|
||||
class PreviewItem {
|
||||
|
|
@ -28,7 +31,10 @@ class PreviewItem {
|
|||
final bool toRight;
|
||||
final bool isDir;
|
||||
final int size;
|
||||
const PreviewItem(this.rel, this.action, {required this.toRight, this.isDir = false, this.size = 0});
|
||||
const PreviewItem(this.rel, this.action, {required this.toRight, this.isDir = false, this.size = 0, this.newer = false});
|
||||
|
||||
/// 229: [PreviewAction.update] 인데 받는 쪽이 더 새것 (예전 원본으로 새 파일을 덮어씀)
|
||||
final bool newer;
|
||||
}
|
||||
|
||||
/// rsync 의 지우기 옵션인지: --delete · --delete-before/during/delay/after/excluded/missing-args · --del (별칭)
|
||||
|
|
@ -115,9 +121,13 @@ Future<List<PreviewItem>> previewSync(String src, String dst,
|
|||
if (t == null) {
|
||||
out.add(PreviewItem(r, PreviewAction.add, toRight: toRight, size: e.size));
|
||||
} else if (t.size != e.size || t.modified.difference(e.modified).inSeconds.abs() > 2) {
|
||||
// -u: 받는 쪽이 더 새 것이면 건너뜀
|
||||
if (update && t.modified.isAfter(e.modified.add(const Duration(seconds: 2)))) continue;
|
||||
out.add(PreviewItem(r, PreviewAction.update, toRight: toRight, size: e.size));
|
||||
// -u: 받는 쪽이 더 새 것이면 건너뜀. 229: 건너뛰는 것도 · 더 새 것을 덮어쓰는 것도 목록에 따로
|
||||
final newer = t.modified.isAfter(e.modified.add(const Duration(seconds: 2)));
|
||||
if (update && newer) {
|
||||
out.add(PreviewItem(r, PreviewAction.skipNewer, toRight: toRight, size: e.size));
|
||||
continue;
|
||||
}
|
||||
out.add(PreviewItem(r, PreviewAction.update, toRight: toRight, size: e.size, newer: newer));
|
||||
}
|
||||
}
|
||||
for (final e in there.values) {
|
||||
|
|
|
|||
|
|
@ -151,6 +151,13 @@ class _CopySyncSettingsState extends State<CopySyncSettings> {
|
|||
onChanged: (v) => c.updateSettings((x) => x.copyConflict = v ?? 'ask'),
|
||||
),
|
||||
),
|
||||
// 229: 폴더 안의 것을 맞출 때 (모니터링의 현재 방식 · WebDAV) - rsync 의 -u 와 같다
|
||||
SwitchListTile(
|
||||
value: s.copySkipNewer,
|
||||
onChanged: (v) => c.updateSettings((x) => x.copySkipNewer = v),
|
||||
title: Text(tr('받는 쪽이 더 새것이면 건너뛰기')),
|
||||
subtitle: Text(tr('현재 방식 · WebDAV 로 폴더 안의 것을 맞출 때 (rsync 의 -u 와 같음). 건너뛴 파일은 이동이라도 원본에 남깁니다')),
|
||||
),
|
||||
SettingTile(
|
||||
title: Text(tr('속도 제한 (KB/s)')),
|
||||
subtitle: Text(tr('0 = 제한 없음. 모든 방법에 적용: rsync --bwlimit, 현재 방식은 앱이 조절, robocopy 는 /IPG 로 비슷하게. '
|
||||
|
|
|
|||
|
|
@ -2163,7 +2163,10 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
: [
|
||||
for (final t in tasks)
|
||||
...await previewSync(t.sources.first, t.dest,
|
||||
toRight: t.sources.first == l, delete: deletes(), update: optionsUpdate(opts(t))),
|
||||
toRight: t.sources.first == l,
|
||||
delete: deletes(),
|
||||
// 229: WebDAV 가 끼면 앱이 직접 - 환경 설정 "받는 쪽이 더 새것이면 건너뛰기" 도 따른다
|
||||
update: optionsUpdate(opts(t)) || (_viaDav([l, r]) && c.settings.copySkipNewer)),
|
||||
],
|
||||
// 97: 다시 비교하는 동안은 예전 결과로 실행하지 못하게
|
||||
onStart: () => set(() {
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ class _CopyCardState extends State<_CopyCard> {
|
|||
Future<void> _run(CopyTask t) async {
|
||||
// 104: 지우기가 들어 있으면 Rsync 화면과 같은 미리 보기를 거친다 (그사이 대상에 생긴 파일을 묻지 않고 지우지 않게).
|
||||
// 210: 이동 (원본 지우기) 도 - 카드의 [실행] 은 따로 묻는 창이 없어 곧바로 원본을 지웠다. 복사만은 지금처럼 바로
|
||||
if (!await confirmDeletingRun(context, t, includeMove: true) || !mounted) return;
|
||||
if (!await confirmDeletingRun(context, t, includeMove: true, skipNewer: widget.c.settings.copySkipNewer) || !mounted) return;
|
||||
widget.c.note(trf('rsync 시작 ({0}): {1} → {2}', [tr('모니터링의 [실행]'), t.sources.join(', '), t.dest]));
|
||||
String? exe;
|
||||
if (t.method == 'rsync') {
|
||||
|
|
|
|||
|
|
@ -98,12 +98,15 @@ class _SyncPreviewViewState extends State<SyncPreviewView> {
|
|||
items.where((x) => x.action == a && (right == null || x.toRight == right)).length;
|
||||
final dels = n(PreviewAction.delete);
|
||||
final overwrites = n(PreviewAction.update);
|
||||
final overNewer = items.where((x) => x.action == PreviewAction.update && x.newer).length;
|
||||
final skipped = n(PreviewAction.skipNewer);
|
||||
final more = items.length >= widget.limit;
|
||||
final parts = [
|
||||
for (final right in [true, false])
|
||||
if (n(PreviewAction.add, right) + n(PreviewAction.update, right) > 0)
|
||||
trf('{0} 새로 {1} · 덮어씀 {2}', [right ? '→' : '←', n(PreviewAction.add, right), n(PreviewAction.update, right)]),
|
||||
if (n(PreviewAction.onlyTarget) > 0) trf('받는 쪽에만 있음 {0} (그대로 둠)', [n(PreviewAction.onlyTarget)]),
|
||||
if (skipped > 0) trf('받는 쪽이 더 새것 {0} (건너뜀)', [skipped]),
|
||||
];
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
|
||||
Row(children: [
|
||||
|
|
@ -122,6 +125,10 @@ class _SyncPreviewViewState extends State<SyncPreviewView> {
|
|||
if (overwrites > 0)
|
||||
Text(trf('받는 쪽에서 {0}개를 덮어씁니다 (내용이 다른 같은 이름 - 원래 것은 없어짐)', [overwrites]),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent, fontWeight: FontWeight.w600)),
|
||||
// 229: 받는 쪽이 더 새 파일을 예전 원본으로 덮어쓰게 되면 따로 크게 (건너뛰기를 끄거나 rsync 에 -u 가 없을 때)
|
||||
if (overNewer > 0)
|
||||
Text(trf('그중 {0}개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다', [overNewer]),
|
||||
style: const TextStyle(fontSize: 12.5, color: Colors.orangeAccent, fontWeight: FontWeight.w800)),
|
||||
if (items.isNotEmpty)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 6),
|
||||
|
|
@ -139,7 +146,9 @@ class _SyncPreviewViewState extends State<SyncPreviewView> {
|
|||
Text(
|
||||
switch (x.action) {
|
||||
PreviewAction.add => '${_arrow(x)} + ${x.rel}${x.isDir ? '/' : ' (${formatSize(x.size)})'}',
|
||||
PreviewAction.update => '${_arrow(x)} ~ ${x.rel} (${formatSize(x.size)}) ${tr('(덮어씀)')}',
|
||||
PreviewAction.update =>
|
||||
'${_arrow(x)} ~ ${x.rel} (${formatSize(x.size)}) ${x.newer ? tr('(더 새 파일을 덮어씀)') : tr('(덮어씀)')}',
|
||||
PreviewAction.skipNewer => ' · ${x.rel} ${tr('(받는 쪽이 더 새것 - 건너뜀)')}',
|
||||
PreviewAction.onlyTarget => ' = ${x.rel}${x.isDir ? '/' : ''} ${tr('(받는 쪽에만 있음)')}',
|
||||
PreviewAction.delete => '${_arrow(x)} − ${x.rel}${x.isDir ? '/' : ''} ${tr('(지워짐)')}',
|
||||
},
|
||||
|
|
@ -149,7 +158,7 @@ class _SyncPreviewViewState extends State<SyncPreviewView> {
|
|||
color: switch (x.action) {
|
||||
PreviewAction.delete => Colors.redAccent,
|
||||
PreviewAction.update => Colors.orangeAccent,
|
||||
PreviewAction.onlyTarget => JjColors.textDim,
|
||||
PreviewAction.onlyTarget || PreviewAction.skipNewer => JjColors.textDim,
|
||||
_ => null,
|
||||
},
|
||||
),
|
||||
|
|
@ -173,14 +182,16 @@ bool taskDeletes(CopyTask t) {
|
|||
|
||||
/// 104: 지우기가 들어 있는 작업은 어디서 실행하든 (모니터링 [실행] · 옵션 저장 뒤 실행 · 최종 정리) Rsync 화면과 같은 미리 보기를 거친다.
|
||||
/// 지울 목록이 나온 뒤에만 [실행] 이 켜진다. 지우기가 없으면 묻지 않고 true
|
||||
/// [includeMove] (210): 이동 (원본 파일 지우기) 도 미리 보기 · 확인을 거친다 - 모니터링의 [실행] 처럼 따로 묻는 창이 없을 때
|
||||
Future<bool> confirmDeletingRun(BuildContext context, CopyTask t, {bool includeMove = false}) async {
|
||||
/// [includeMove] (210): 이동 (원본 파일 지우기) 도 미리 보기 · 확인을 거친다 - 모니터링의 [실행] 처럼 따로 묻는 창이 없을 때.
|
||||
/// [skipNewer] (229): 앱이 직접 할 때 (현재 방식 · WebDAV) 받는 쪽이 더 새것이면 건너뛰는지 (환경 설정)
|
||||
Future<bool> confirmDeletingRun(BuildContext context, CopyTask t, {bool includeMove = false, bool skipNewer = false}) async {
|
||||
final deletes = taskDeletes(t);
|
||||
final moves = includeMove && t.move;
|
||||
if (!deletes && !moves) return true;
|
||||
List<PreviewItem>? preview;
|
||||
Object? error;
|
||||
final update = optionsUpdate(t.options);
|
||||
final direct = t.method == 'builtin' || [...t.sources, t.dest].any(isDav);
|
||||
final update = optionsUpdate(t.options) || (direct && skipNewer);
|
||||
String dstOf(String src) => t.contents ? t.dest : vJoin(t.dest, vBasename(src));
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jj_mkvmaker/app/transfer_job.dart';
|
||||
import 'package:jj_mkvmaker/core/sync_preview.dart';
|
||||
import 'package:jj_mkvmaker/app/settings.dart';
|
||||
import 'package:jj_mkvmaker/core/sync_tools.dart' show CopyMethod;
|
||||
import 'package:jj_mkvmaker/core/webdav.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
|
@ -109,6 +111,42 @@ void main() {
|
|||
// (속성만 다른 "수정됨" 파일은 /IS · /IT 로도 남는다 - 그때는 216 의 "n개는 원본에 남음" 으로 알린다)
|
||||
});
|
||||
|
||||
test('229: 앱 직접 이동 - 받는 쪽이 더 새것이면 덮어쓰지 않고 원본도 남긴다 (환경 설정 처음 값 켜짐)', () async {
|
||||
expect(AppSettings.fromJson({}).copySkipNewer, isTrue);
|
||||
expect(AppSettings.fromJson((AppSettings()..copySkipNewer = false).toJson()).copySkipNewer, isFalse);
|
||||
final old = File(p.join(src, 'older.txt'))..writeAsStringSync('old in src');
|
||||
old.setLastModifiedSync(DateTime.now().subtract(const Duration(days: 2)));
|
||||
File(p.join(dst, 'older.txt')).writeAsStringSync('NEW version in dst');
|
||||
final job = TransferJob(sources: [src], dest: dst, move: true, contents: true, skipNewer: true);
|
||||
await job.run();
|
||||
expect(job.error, isNull);
|
||||
expect(File(p.join(dst, 'older.txt')).readAsStringSync(), 'NEW version in dst', reason: '새 내용 그대로');
|
||||
expect(old.existsSync(), isTrue, reason: '건너뛴 원본은 이동이라도 남김');
|
||||
expect(File(p.join(src, 'a.txt')).existsSync(), isFalse, reason: '나머지는 옮김');
|
||||
expect(job.leftInSource, 1);
|
||||
expect(job.doneSummary(), contains('1개는 원본에 남음'));
|
||||
});
|
||||
|
||||
test('229: 건너뛰기를 끄면 (사용자가 고름) 예전처럼 덮어쓴다', () async {
|
||||
final old = File(p.join(src, 'older.txt'))..writeAsStringSync('old in src');
|
||||
old.setLastModifiedSync(DateTime.now().subtract(const Duration(days: 2)));
|
||||
File(p.join(dst, 'older.txt')).writeAsStringSync('NEW version in dst');
|
||||
final job = TransferJob(sources: [src], dest: dst, move: false, contents: true, skipNewer: false);
|
||||
await job.run();
|
||||
expect(File(p.join(dst, 'older.txt')).readAsStringSync(), 'old in src');
|
||||
});
|
||||
|
||||
test('229: 미리 보기 - 건너뛰면 "받는 쪽이 더 새것 - 건너뜀", 아니면 덮어쓸 줄에 "더 새 것" 표시', () async {
|
||||
final old = File(p.join(src, 'older.txt'))..writeAsStringSync('old in src');
|
||||
old.setLastModifiedSync(DateTime.now().subtract(const Duration(days: 2)));
|
||||
File(p.join(dst, 'older.txt')).writeAsStringSync('NEW version in dst');
|
||||
final skip = await previewSync(src, dst, toRight: true, update: true);
|
||||
expect([for (final x in skip) if (x.rel == 'older.txt') x.action], [PreviewAction.skipNewer]);
|
||||
final over = await previewSync(src, dst, toRight: true);
|
||||
final o = over.firstWhere((x) => x.rel == 'older.txt');
|
||||
expect([o.action, o.newer], [PreviewAction.update, true]);
|
||||
});
|
||||
|
||||
group('214 b: WebDAV', () {
|
||||
late Directory remote;
|
||||
late TestDavServer server;
|
||||
|
|
@ -138,6 +176,20 @@ void main() {
|
|||
expect(File(p.join(remote.path, 'a.txt')).readAsStringSync(), 'a');
|
||||
});
|
||||
|
||||
test('229 (WebDAV 로): 받는 쪽이 더 새것이면 건너뛰고 원본 남김', () async {
|
||||
HttpOverrides.global = null;
|
||||
File(p.join(src, 'older.txt'))
|
||||
..writeAsStringSync('old in src')
|
||||
..setLastModifiedSync(DateTime.now().subtract(const Duration(days: 2)));
|
||||
File(p.join(remote.path, 'older.txt')).writeAsStringSync('NEW version in dst');
|
||||
final job = TransferJob(sources: [src], dest: 'dav://nas/', move: true, contents: true, skipNewer: true);
|
||||
await job.run();
|
||||
expect(job.error, isNull);
|
||||
expect(File(p.join(remote.path, 'older.txt')).readAsStringSync(), 'NEW version in dst');
|
||||
expect(File(p.join(src, 'older.txt')).existsSync(), isTrue);
|
||||
expect(File(p.join(src, 'a.txt')).existsSync(), isFalse);
|
||||
});
|
||||
|
||||
test('214 d (WebDAV 로): 로컬 원본의 링크는 따라가지 않는다', () async {
|
||||
HttpOverrides.global = null;
|
||||
final outside = Directory(p.join(tmp.path, 'outside2'))..createSync();
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ void main() {
|
|||
test('→ --delete -u: 받는 쪽에만 있는 것은 지워짐 · 받는 쪽이 새것이면 건너뜀', () async {
|
||||
final r = m(await previewSync(a, b, toRight: true, delete: true, update: true));
|
||||
expect(r['only_b.txt'], PreviewAction.delete);
|
||||
expect(r.containsKey('leftold.txt'), isFalse);
|
||||
// 229: 건너뛰는 것도 목록에 "받는 쪽이 더 새것 - 건너뜀" 으로 (예전에는 빠져 있었다)
|
||||
expect(r['leftold.txt'], PreviewAction.skipNewer);
|
||||
expect(r.containsKey('half.mkv.jjpart'), isFalse);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -32,4 +32,22 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('덮어씁니다'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('229: 받는 쪽이 더 새것 - 건너뛰면 "(건너뜀)", 덮어쓰게 되면 따로 굵게 경고', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Scaffold(
|
||||
body: SyncPreviewView(
|
||||
compute: () async => const [
|
||||
PreviewItem('skip.txt', PreviewAction.skipNewer, toRight: true, size: 3),
|
||||
PreviewItem('older.txt', PreviewAction.update, toRight: true, size: 13, newer: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('받는 쪽이 더 새것 1 (건너뜀)'), findsOneWidget);
|
||||
expect(find.text('그중 1개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다'), findsOneWidget);
|
||||
expect(find.textContaining('(더 새 파일을 덮어씀)'), findsOneWidget);
|
||||
expect(find.textContaining('(받는 쪽이 더 새것 - 건너뜀)'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue