import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as p; import '../l10n/tr.dart'; /// 관리자 지시 (10/20, 28 재현 - P0 데이터): Windows 의 rsync (MSYS2 · Cygwin 런타임) 는 Windows 프로그램이 넘긴 명령 줄을 /// 스스로 다시 읽는다 - ' 를 따옴표로 짝지어 (--exclude=it's.txt --exclude=Bob's.txt 가 한 인수로 합쳐져 거르기가 사라지고 /// 지킬 파일이 지워짐, ' 가 든 경로는 합쳐져 실패) * ? [ ] { } 를 작업 폴더의 파일로 펼친다 (--exclude *.log 가 x.log y.log 로). /// 앱이 만든 인수를 응답 파일 (@파일) 에 큰따옴표로 감싸 넣고 MSYS=noglob 으로 띄우면 그대로 간다 (실제 rsync 3.5.1 로 확인 - /// MSYS=noglob 만으로는 ' 를 막지 못하고, CYGWIN=noglob 은 소용없다). /// 응답 파일의 경로에 빈칸 · ' 가 있으면 @ 가 먹히지 않아, 그 파일이 든 폴더를 작업 폴더로 하고 "@args.txt" 로 넘긴다. /// 큰따옴표 (") 가 든 인수는 이 방법으로도 그대로 넘기지 못한다 - 실행하지 않고 이유를 알린다 (Windows 파일 이름에는 " 가 없다). class RsyncLaunch { final List args; final String? workingDirectory; final Map? environment; final Directory? _dir; RsyncLaunch._(this.args, this.workingDirectory, this.environment, this._dir); /// 다 쓴 응답 파일을 지운다 (rsync 가 끝난 뒤) void dispose() { try { _dir?.deleteSync(recursive: true); } catch (_) {} } } /// Windows rsync 에 그대로 넘길 수 없는 인수 (큰따옴표가 듦) List msysUnsafeArgs(List args) => [for (final a in args) if (a.contains('"')) a]; /// [args] 를 rsync 에 그대로 넘기도록 준비한다 (Windows 만 응답 파일 · MSYS=noglob, 그 밖은 그대로). 끝나면 [RsyncLaunch.dispose] Future prepareRsyncLaunch(List args, {bool? windows}) async { if (!(windows ?? Platform.isWindows)) return RsyncLaunch._(args, null, null, null); final bad = msysUnsafeArgs(args); if (bad.isNotEmpty) { throw StateError(trf('큰따옴표 (") 가 든 인수는 Windows 의 rsync 에 그대로 넘길 수 없어 실행하지 않았습니다: {0}', [bad.join(' · ')])); } final dir = await Directory.systemTemp.createTemp('jj_rsargs_'); await File(p.join(dir.path, 'args.txt')).writeAsString(args.map((a) => '"$a"').join(' '), encoding: utf8); final msys = Platform.environment['MSYS']; return RsyncLaunch._(['@args.txt'], dir.path, {'MSYS': msys == null || msys.trim().isEmpty ? 'noglob' : '$msys noglob'}, dir); }