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