폴더 안의 것을 바로 위 폴더로 이동이 조용히 안 되던 것 (205)

사용자 지적 "rsync 로 이동이 안 되는 경우가 있어" (버그 수정): Rsync 화면 (원본/ → 대상/) 에서 바로 위 폴더로 이동하면
실행 때의 검사가 폴더째 옮기는 규칙 ("이미 이 폴더에 있습니다") 으로 막아, 알림 없이 아무 일도 하지 않았다.
안의 것을 옮길 때 (contents) 는 이 검사를 하지 않는다. 그래도 시작하지 못하면 이유를 화면 · 작업 기록에 알린다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix_mkv
RyuJongJin 2026-10-11 21:09:32 +09:00
parent 996491547b
commit 3ad8becafa
8 changed files with 40 additions and 5 deletions

View File

@ -285,9 +285,11 @@
"rsync 내려받기": "Download rsync",
"rsync 는 처음 쓸 때 내려받습니다 (MSYS2 공식 저장소의 rsync 3.5.1 · 약 8MB, 파일마다 SHA256 확인).\n설정 폴더의 rsync 에 두어 업데이트해도 남습니다. 지금 받을까요?": "rsync is downloaded on first use (rsync 3.5.1 from the official MSYS2 repository · about 8MB, SHA256 checked for each file).\nIt is kept in the rsync folder of the settings folder and survives updates. Download now?",
"rsync 를 받지 못했습니다: {0}": "Could not download rsync: {0}",
"rsync 를 실행할 수 없습니다": "rsync cannot be run",
"rsync 를 항목마다 따로 실행하거나, 한 번에 실행 (robocopy 는 늘 폴더마다)": "Run rsync separately for each item, or once for all (robocopy always runs per folder)",
"rsync 받는 중": "Downloading rsync",
"rsync 시작 ({0}): {1} → {2}": "rsync started ({0}): {1} → {2}",
"rsync 시작하지 못함: {0}": "rsync did not start: {0}",
"rsync 실행 파일": "rsync executable",
"rsync 실행 파일을 찾을 수 없습니다. 환경 설정 > Rsync 에서 확인하세요.": "Cannot find the rsync executable. Check Settings > Rsync.",
"rsync 실행 파일을 찾을 수 없습니다. 환경 설정 > 파일 탐색기에서 경로를 확인하세요.": "rsync executable not found. Check the path in Settings > File explorer.",
@ -1149,6 +1151,7 @@
"시작 안 함 (모니터링에서 시작)": "Don't start (start from Monitor)",
"시작: {0} → {1}\\{2}{3}": "Started: {0} → {1}\\{2}{3}",
"시작하는 중…": "Starting…",
"시작하지 못했습니다: {0}": "Could not start: {0}",
"시작할 동기화를 고르세요. 고르지 않은 것은 이번 실행 동안 멈춥니다 (모니터링 > lsync 에서 시작).": "Choose which syncs to start. The others stay paused for this session (start them in Monitor > lsync).",
"시작할 때 새 버전 확인 (하루 한 번)": "Check for a new version at startup (once a day)",
"시작할 때마다 설정 창 보기": "Show the setup window at every start",

View File

@ -285,9 +285,11 @@
"rsync 내려받기": "rsync をダウンロード",
"rsync 는 처음 쓸 때 내려받습니다 (MSYS2 공식 저장소의 rsync 3.5.1 · 약 8MB, 파일마다 SHA256 확인).\n설정 폴더의 rsync 에 두어 업데이트해도 남습니다. 지금 받을까요?": "rsync は初めて使うときにダウンロードします (MSYS2 公式リポジトリの rsync 3.5.1 · 約 8MB、ファイルごとに SHA256 を確認)。\n設定フォルダーの rsync に置くため、更新しても残ります。今ダウンロードしますか?",
"rsync 를 받지 못했습니다: {0}": "rsync をダウンロードできませんでした: {0}",
"rsync 를 실행할 수 없습니다": "rsync を実行できません",
"rsync 를 항목마다 따로 실행하거나, 한 번에 실행 (robocopy 는 늘 폴더마다)": "rsync を項目ごとに実行するか、まとめて 1 回で実行 (robocopy は常にフォルダーごと)",
"rsync 받는 중": "rsync をダウンロード中",
"rsync 시작 ({0}): {1} → {2}": "rsync 開始 ({0}): {1} → {2}",
"rsync 시작하지 못함: {0}": "rsync を開始できませんでした: {0}",
"rsync 실행 파일": "rsync 実行ファイル",
"rsync 실행 파일을 찾을 수 없습니다. 환경 설정 > Rsync 에서 확인하세요.": "rsync 実行ファイルが見つかりません。環境設定 > Rsync を確認してください。",
"rsync 실행 파일을 찾을 수 없습니다. 환경 설정 > 파일 탐색기에서 경로를 확인하세요.": "rsync 実行ファイルが見つかりません。環境設定 > ファイルエクスプローラーでパスを確認してください。",
@ -1149,6 +1151,7 @@
"시작 안 함 (모니터링에서 시작)": "開始しない (モニタリングで開始)",
"시작: {0} → {1}\\{2}{3}": "開始: {0} → {1}\\{2}{3}",
"시작하는 중…": "開始中…",
"시작하지 못했습니다: {0}": "開始できませんでした: {0}",
"시작할 동기화를 고르세요. 고르지 않은 것은 이번 실행 동안 멈춥니다 (모니터링 > lsync 에서 시작).": "開始する同期を選んでください。選ばなかったものは今回の実行中は停止します (モニタリング > lsync で開始)。",
"시작할 때 새 버전 확인 (하루 한 번)": "起動時に新しいバージョンを確認(1日1回)",
"시작할 때마다 설정 창 보기": "起動するたびに設定ウィンドウを表示",

View File

@ -285,9 +285,11 @@
"rsync 내려받기": "下载 rsync",
"rsync 는 처음 쓸 때 내려받습니다 (MSYS2 공식 저장소의 rsync 3.5.1 · 약 8MB, 파일마다 SHA256 확인).\n설정 폴더의 rsync 에 두어 업데이트해도 남습니다. 지금 받을까요?": "rsync 在首次使用时下载 (MSYS2 官方仓库的 rsync 3.5.1 · 约 8MB,逐个文件校验 SHA256)。\n保存在设置文件夹的 rsync 中,更新后仍保留。现在下载吗?",
"rsync 를 받지 못했습니다: {0}": "无法下载 rsync: {0}",
"rsync 를 실행할 수 없습니다": "无法运行 rsync",
"rsync 를 항목마다 따로 실행하거나, 한 번에 실행 (robocopy 는 늘 폴더마다)": "rsync 按项目分别执行,或一次执行 (robocopy 总是按文件夹)",
"rsync 받는 중": "正在下载 rsync",
"rsync 시작 ({0}): {1} → {2}": "rsync 开始 ({0}): {1} → {2}",
"rsync 시작하지 못함: {0}": "rsync 未能启动:{0}",
"rsync 실행 파일": "rsync 可执行文件",
"rsync 실행 파일을 찾을 수 없습니다. 환경 설정 > Rsync 에서 확인하세요.": "找不到 rsync 可执行文件。请检查 设置 > Rsync。",
"rsync 실행 파일을 찾을 수 없습니다. 환경 설정 > 파일 탐색기에서 경로를 확인하세요.": "找不到 rsync 可执行文件。请在 设置 > 文件浏览器 中检查路径。",
@ -1149,6 +1151,7 @@
"시작 안 함 (모니터링에서 시작)": "不开始 (在监控中开始)",
"시작: {0} → {1}\\{2}{3}": "开始: {0} → {1}\\{2}{3}",
"시작하는 중…": "正在开始…",
"시작하지 못했습니다: {0}": "无法开始:{0}",
"시작할 동기화를 고르세요. 고르지 않은 것은 이번 실행 동안 멈춥니다 (모니터링 > lsync 에서 시작).": "请选择要开始的同步。未选的在本次运行期间暂停 (可在 监控 > lsync 中开始)。",
"시작할 때 새 버전 확인 (하루 한 번)": "启动时检查新版本(每天一次)",
"시작할 때마다 설정 창 보기": "每次启动时显示设置窗口",

View File

@ -95,7 +95,7 @@ class CopyCenter extends ChangeNotifier {
/// 실행 · 재개. 원본 · 대상이 없거나 폴더를 자기 안으로 넣는 것이면 시작하지 않고 실패로 남긴다 (null).
Future<TransferJob?> start(CopyTask t, {String? rsyncExe, NameConflict conflict = NameConflict.rename}) async {
if (isRunning(t.id)) return jobs[t.id];
final problem = transferProblem(t.sources, t.dest, move: t.move);
final problem = transferProblem(t.sources, t.dest, move: t.move, contents: t.contents);
if (problem != null) {
if (tasks.any((x) => x.id == t.id)) {
await update(t.copyWith(lastRun: DateTime.now().toIso8601String(), lastResult: 'failed', lastMessage: problem));

View File

@ -152,7 +152,8 @@ bool isSameOrInside(String a, String b) {
/// - 원본이 없음 (다른 곳에서 지워졌거나 옮겨짐) · 대상 폴더가 없음
/// - 폴더를 자기 자신 (또는 그 안) 으로 복사 · 이동
/// - 이미 그 폴더에 있는 것을 같은 폴더로 이동
String? transferProblem(List<String> sources, String dest, {required bool move}) {
/// [contents]: 폴더 "안의 것" 을 옮긴다 (Rsync 화면 · 원본/ → 대상/) - 바로 위 폴더로 옮기는 것도 된다 (205)
String? transferProblem(List<String> sources, String dest, {required bool move, bool contents = false}) {
// WebDAV 경로는 네트워크라 여기서 있는지 확인하지 않는다 (없으면 복사할 때 알림)
final missing = [for (final s in sources) if (vMissingSync(s)) s];
if (missing.isNotEmpty) {
@ -163,7 +164,9 @@ String? transferProblem(List<String> sources, String dest, {required bool move})
if ((isDav(s) || FileSystemEntity.isDirectorySync(s)) && isSameOrInside(dest, s)) {
return trf('폴더를 자기 자신 안으로 {0} 수 없습니다: {1}', [move ? tr('옮길') : tr('복사할'), vBasename(s)]);
}
if (move && samePath(vDirname(s), dest)) return trf('이미 이 폴더에 있습니다: {0}', [vBasename(s)]);
// 폴더째 옮길 때만: 이미 그 폴더 안에 있으면 옮길 것이 없다.
// 205: 안의 것을 바로 위 폴더로 올리는 이동 (Down\Movie/ → Down) 은 막지 않는다 (예전에는 조용히 시작하지 않았다)
if (move && !contents && samePath(vDirname(s), dest)) return trf('이미 이 폴더에 있습니다: {0}', [vBasename(s)]);
}
return null;
}

View File

@ -2236,7 +2236,14 @@ class _ExplorerPageState extends State<ExplorerPage> {
for (var i = 0; i < run.length; i++) {
c.note(trf('rsync 시작 ({0}): {1} → {2}', [tr('Rsync 화면 확인 창의 [실행]'), run[i].sources.first, run[i].dest]));
final j = await center.start(run[i].copyWith(options: shownOpts[i]), rsyncExe: dav ? null : exe);
if (j == null || !mounted) break;
if (!mounted) break;
if (j == null) {
// 205: 시작하지 못하면 이유를 알린다 (예전에는 알림 없이 끝나 "이동이 안 됨" 으로만 보였다)
final why = transferProblem(run[i].sources, run[i].dest, move: run[i].move, contents: run[i].contents);
_snack(trf('시작하지 못했습니다: {0}', [why ?? tr('rsync 를 실행할 수 없습니다')]));
c.note(trf('rsync 시작하지 못함: {0}', [why ?? '']));
break;
}
jobs.add(j);
setState(() => _jobs = [..._jobs.where((x) => !x.finished), j]);
await j.done;

View File

@ -348,7 +348,7 @@ class _CopyCardState extends State<_CopyCard> {
return;
}
}
final problem = transferProblem(t.sources, t.dest, move: t.move);
final problem = transferProblem(t.sources, t.dest, move: t.move, contents: t.contents);
await center.start(t, rsyncExe: exe);
if (problem != null && mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(problem)));
}

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/app/app_controller.dart';
import 'package:jj_mkvmaker/app/copy_center.dart';
import 'package:jj_mkvmaker/core/file_ops.dart' show transferProblem;
import 'package:jj_mkvmaker/app/live_sync.dart';
import 'package:jj_mkvmaker/app/settings.dart';
import 'package:jj_mkvmaker/core/cron_window.dart';
@ -67,6 +68,21 @@ void main() {
});
tearDown(() => tmp.deleteSync(recursive: true));
test('205: 폴더 안의 것을 바로 위 폴더로 이동 (Rsync 화면 · 원본/ → 대상/) 은 막지 않고 옮긴다', () async {
final up = p.dirname(src);
// 폴더째 옮기기는 그대로 "이미 이 폴더에 있습니다", 안의 것은 된다
expect(transferProblem([src], up, move: true), contains('이미 이 폴더에'));
expect(transferProblem([src], up, move: true, contents: true), isNull);
final center = CopyCenter(c);
final t = await center.remember([src], up, contents: true, method: 'builtin', move: true);
final job = await center.start(t);
expect(job, isNotNull, reason: '예전에는 여기서 null (조용히 시작하지 않음)');
await job!.done;
expect(job.error, isNull);
expect(File(p.join(up, 'sub', 'b.txt')).readAsStringSync(), 'b');
expect(Directory(src).listSync(recursive: true).whereType<File>(), isEmpty, reason: '원본의 파일은 옮겨짐');
});
test('같은 복사는 기억한 옵션 · 실행 결과 기록 · lsync 로 이동 · 다시 복사 목록으로', () async {
c.settings.copyMethodFolder = 'builtin'; // rsync 실행은 sync_tools_test (시험 환경에서는 path_provider 없음)
final center = CopyCenter(c);