JJ_MKVMaker/lib/core/sync/copy_output.dart

103 lines
4.0 KiB
Dart

// 약속 (나누기 sync_tools): rsync · robocopy 출력 읽기 - 조각을 받을 때마다 "다 보낸 파일" 줄과 진행률 · 속도 · 확인한 수.
// 입력: 출력 조각 (줄 중간에서 끊겨도 됨 - 남은 것은 다음 조각과 이어 읽음). 출력: 다 보낸 파일 (원본 기준 상대 경로) 목록.
// 오류: 던지지 않는다 (모르는 줄은 건너뜀).
/// rsync -v 출력에서 "다 보낸 파일" 줄을 골라낸다 (-P 의 \r 진행 줄 · 머리글 · 요약 · 폴더 줄은 뺌).
/// 결과: 원본 기준 상대 경로 (예: "sub/inner.txt")
class RsyncOutput {
final _buf = StringBuffer();
/// 지금 파일의 진행률 (-P 의 " 1,234 45% 1.2MB/s" 줄)
double? currentPercent;
/// 지금 전송 속도 (바이트/초, 같은 줄의 "1.2MB/s")
double? currentSpeed;
/// 폴더 전체에서 확인한 항목 (진행 줄의 "to-chk=남은/전체" · "ir-chk=…"). 모르면 null.
int? checked, checkTotal;
static final _chkRx = RegExp(r'(?:to|ir)-chk=(\d+)/(\d+)');
static final _speedRx = RegExp(r'(\d+(?:\.\d+)?)([kKMGT]?)B/s');
List<String> feed(String chunk) {
_buf.write(chunk);
final text = _buf.toString();
final cut = text.lastIndexOf(RegExp(r'[\r\n]'));
if (cut < 0) return const [];
_buf
..clear()
..write(text.substring(cut + 1));
final done = <String>[];
for (final raw in text.substring(0, cut).split(RegExp(r'[\r\n]'))) {
final line = raw.trimRight();
if (line.isEmpty) continue;
if (line.startsWith(' ')) {
final m = RegExp(r'\s(\d{1,3})%\s').firstMatch(line);
if (m != null) currentPercent = int.parse(m[1]!) / 100;
final ck = _chkRx.firstMatch(line);
if (ck != null) {
checkTotal = int.parse(ck[2]!);
checked = checkTotal! - int.parse(ck[1]!);
}
final sp = _speedRx.firstMatch(line);
if (sp != null) {
const mul = {'': 1, 'k': 1024, 'K': 1024, 'M': 1024 * 1024, 'G': 1024 * 1024 * 1024, 'T': 1024 * 1024 * 1024 * 1024};
currentSpeed = double.parse(sp[1]!) * mul[sp[2]!]!;
}
continue;
}
if (line.endsWith('/')) continue; // 폴더
if (line.startsWith('sending ') ||
line.startsWith('receiving ') ||
line.startsWith('sent ') ||
line.startsWith('total size') ||
line.startsWith('created directory') ||
line.startsWith('deleting ') ||
line.startsWith('rsync:') ||
line.startsWith('rsync error') ||
line.contains(' -> ')) {
continue;
}
currentPercent = null;
done.add(line);
}
return done;
}
}
/// robocopy 출력에서 파일 줄 ("New File", "Newer", "Older", "Changed" … 뒤의 크기 · 이름) 을 센다
class RobocopyOutput {
final _buf = StringBuffer();
double? currentPercent;
/// 파일 줄: 탭 · 종류 · 탭 탭 · 크기 · 탭 · 경로. 61: 종류 글은 Windows 언어마다 다르므로
/// ("New File" · "새 파일" · "新しいファイル" · "新文件") 글이 아니라 탭 모양으로 알아본다.
/// 종류가 * 로 시작하면 (*EXTRA File 등) 대상에만 있는 것이라 세지 않는다.
static final _file = RegExp(r'^\t+\s*([^\t\s\d][^\t]*?)\s*\t+\s*[\d.]+\s*[kmgt]?\s*\t(.+)$', caseSensitive: false);
List<String> feed(String chunk) {
_buf.write(chunk);
final text = _buf.toString();
final cut = text.lastIndexOf(RegExp(r'[\r\n]'));
if (cut < 0) return const [];
_buf
..clear()
..write(text.substring(cut + 1));
final done = <String>[];
for (final raw in text.substring(0, cut).split(RegExp(r'[\r\n]'))) {
final line = raw.trim();
final pct = RegExp(r'^(\d{1,3}(?:\.\d)?)%$').firstMatch(line);
if (pct != null) {
currentPercent = double.parse(pct[1]!) / 100;
continue;
}
final m = _file.firstMatch(raw);
if (m != null && !m[1]!.startsWith('*')) {
currentPercent = null;
done.add(m[2]!.trim());
}
}
return done;
}
}