103 lines
4.0 KiB
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;
|
|
}
|
|
}
|