96 lines
5.0 KiB
Dart
96 lines
5.0 KiB
Dart
/// 약속: 실시간 동기화가 부르는 도구 - 앱에 들어 있는 rsync 찾기 · 지금 설정으로 쓸 rsync · 쓸 수 있는 복사 방법 ·
|
|
/// 쌍마다 rsync · robocopy 인수 ([SyncCommands]). 지우기 (--delete · /PURGE) 는 쌍의 "지우기 포함" 으로만 넣는다 (97),
|
|
/// 원본을 지우는 옵션은 넣지 않는다 (107).
|
|
library;
|
|
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/services.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
import '../../core/file_ops.dart';
|
|
import '../../core/sync_preview.dart' show isRsyncDeleteOption, isSourceRemovingOption;
|
|
import '../../core/sync_tools.dart';
|
|
import '../../platform/windows/rsync_installer.dart';
|
|
import '../settings.dart';
|
|
|
|
/// 앱에 들어 있는 rsync (없으면 null). 시작할 때 [findBundledRsync] 로 정한다.
|
|
/// - Windows: 실행 파일 옆 rsync/rsync.exe (tool/fetch_rsync.ps1 로 준비해 빌드에 넣음)
|
|
/// - Android: 네이티브 라이브러리 폴더의 librsync.so (tool/build_rsync_android.sh, APK 의 jniLibs)
|
|
String? bundledRsync;
|
|
|
|
Future<void> findBundledRsync() async {
|
|
String? path;
|
|
if (Platform.isWindows) {
|
|
path = p.join(p.dirname(Platform.resolvedExecutable), 'rsync', 'rsync.exe');
|
|
} else if (Platform.isAndroid) {
|
|
try {
|
|
final dir = await const MethodChannel('jj_mkvmaker/android').invokeMethod<String>('nativeLibDir');
|
|
if (dir != null) path = p.join(dir, 'librsync.so');
|
|
} catch (_) {}
|
|
}
|
|
bundledRsync = path != null && File(path).existsSync() ? path : null;
|
|
}
|
|
|
|
/// 지금 설정으로 쓸 rsync 실행 파일 (없으면 null).
|
|
/// - 직접 지정: 그 파일
|
|
/// - 기본: 앱에 들어 있는 것, 없으면 (Windows) 내려받아 둔 것
|
|
Future<String?> rsyncExecutable(AppSettings s) async {
|
|
if (s.copy.rsyncSource == 'custom') {
|
|
return s.copy.rsyncPath.isNotEmpty && File(s.copy.rsyncPath).existsSync() ? s.copy.rsyncPath : null;
|
|
}
|
|
final b = bundledRsync;
|
|
if (b != null && File(b).existsSync()) return b;
|
|
if (!Platform.isWindows) return null;
|
|
return RsyncInstaller.installedPath();
|
|
}
|
|
|
|
/// 이 기기에서 쓸 수 있는 방법
|
|
bool copyMethodAvailable(CopyMethod m, AppSettings s) => switch (m) {
|
|
CopyMethod.builtin => true,
|
|
CopyMethod.robocopy => Platform.isWindows,
|
|
CopyMethod.rsync => Platform.isWindows || s.copy.rsyncSource == 'custom' || bundledRsync != null,
|
|
};
|
|
|
|
class SyncCommands {
|
|
/// rsync 인수 (실시간 동기화). 지우기는 쌍의 "지우기 포함" 으로만 - 설정의 rsync 옵션에 --delete · --del 이 있어도
|
|
/// 41 · 42 의 확인을 건너뛰지 않게 뺀다 (97)
|
|
static List<String> rsyncArgs(LiveSyncPair x, AppSettings s, {required bool windows}) => [
|
|
// 107: 원본을 지우는 옵션 (--remove-source-files) 도 실시간 동기화에서는 쓰지 않는다
|
|
// 관리자 판단 (10/20, 28 확인): 목록 · 기록 · 배치 파일의 C:\… 는 rsync 가 읽는 꼴로 (rsyncFileOptionArgs)
|
|
...rsyncFileOptionArgs([
|
|
for (final o in splitOptions(s.copy.rsyncOptions))
|
|
if (o != '-P' && o != '--progress' && !isRsyncDeleteOption(o) && !isSourceRemovingOption(o)) o,
|
|
], windows: windows),
|
|
if (!windows) '-8', // 한글 등 이름을 \#355… 로 바꾸지 않고 그대로 (Android 빌드는 iconv 없음)
|
|
if (s.copy.bandwidthKBps > 0) '--bwlimit=${s.copy.bandwidthKBps}',
|
|
if (x.delete) '--delete',
|
|
// 10/20 (15, 28 의 장면으로 찾음): --delete-excluded 는 쌍이 지우기 포함일 때만 남긴다 - 예전에는 늘 빼서, 지울지 묻는 창
|
|
// (diff 는 옵션의 거르기를 따름) 에 보인 거른 것을 실제로는 지우지 않고 맞출 것에 계속 남았다 (원격 쌍 · Rsync 화면은 따름)
|
|
if (x.delete && splitOptions(s.copy.rsyncOptions).contains('--delete-excluded')) '--delete-excluded',
|
|
...appTempRsyncExcludes, // 78 보충: 이름 바꾸는 중의 임시 이름은 보내지도 지우지도 않는다
|
|
'${toCygwinPath(x.source, windows: windows)}/',
|
|
'${rsyncDestPath(x.target, windows: windows)}/', // 받는 쪽 UNC 는 \\서버\… (28 · 15 확인 10/20)
|
|
];
|
|
|
|
/// robocopy 인수 (실시간 동기화). 지우기 (/MIR · /PURGE) 는 쌍의 "지우기 포함" 으로만 (/MIR 는 /E 로) - 97
|
|
static List<String> robocopyArgs(LiveSyncPair x, AppSettings s) {
|
|
final opts = [
|
|
for (final o in splitOptions(s.copy.robocopyOptions))
|
|
if (o.toUpperCase() == '/MIR') '/E' else if (o.toUpperCase() != '/PURGE' && !isSourceRemovingOption(o)) o,
|
|
];
|
|
return [
|
|
x.source,
|
|
x.target,
|
|
...opts,
|
|
if (!opts.any((o) => o.toUpperCase() == '/E')) '/E',
|
|
// 데이터: 링크 · junction 은 따라가지 않는다 (복사 · 이동과 같게 - robocopyExtraOptions)
|
|
if (!robocopyLinkOption(opts)) '/XJ',
|
|
if (x.delete) '/PURGE',
|
|
...appTempRobocopyExcludes, // 78 보충
|
|
if (s.copy.bandwidthKBps > 0) '/IPG:${robocopyIpg(s.copy.bandwidthKBps)}',
|
|
'/BYTES', '/NJH', '/NJS', '/NDL', '/NP',
|
|
];
|
|
}
|
|
}
|