import 'dart:async'; import '../l10n/tr.dart'; /// 관리자 판단 (10/20): 원격 서버 (FTP · SFTP · SMB · WebDAV) 의 받기 · 올리기 자료 연결에서 조각 사이에 정한 시간 동안 /// 아무것도 오가지 않으면 실패한다. 예전에는 서버가 중간에 멈추면 복사 · 동기화가 영원히 멈춰 있었다 (명령의 답에만 시간 제한). /// 정한 시간은 설정 "원격 서버 응답 기다리기" (처음 60초 · 30 · 60 · 120 · 300초) - 앱이 [limit] 에 넣는다 class RemoteIdle { static const choices = [30, 60, 120, 300]; static const defaultSeconds = 60; static Duration limit = const Duration(seconds: defaultSeconds); } /// 조각 사이에 정한 시간이 지나도 서버가 아무것도 주고받지 않음 class RemoteIdleTimeout implements Exception { final int seconds; const RemoteIdleTimeout(this.seconds); @override String toString() => trf('서버가 답하지 않습니다 ({0}초)', [seconds]); } /// 받기: 조각 사이가 [limit] (없으면 [RemoteIdle.limit]) 를 넘으면 [RemoteIdleTimeout] 으로 끝나는 스트림. /// 그때 [onTimeout] 으로 그 연결을 끊고 원래 스트림의 구독도 끊는다 (늦게 오는 조각 · 오류를 받지 않음). /// 받는 쪽이 멈춰 (pause) 있는 동안은 세지 않는다 Stream idleRead(Stream s, {Duration? limit, void Function()? onTimeout}) { final d = limit ?? RemoteIdle.limit; late StreamController out; StreamSubscription? sub; Timer? t; void arm() { t?.cancel(); t = Timer(d, () { final x = sub; sub = null; // 오류를 먼저 넣고 닫은 뒤에 연결을 끊는다 - 먼저 끊으면 원래 스트림의 "끝" 이 먼저 와서 정상으로 끝난 것처럼 보였다 if (!out.isClosed) { out.addError(RemoteIdleTimeout(d.inSeconds)); unawaited(out.close()); } onTimeout?.call(); unawaited(Future(() => x?.cancel()).catchError((_) {})); }); } out = StreamController( onListen: () { arm(); sub = s.listen((v) { if (out.isClosed) return; arm(); out.add(v); }, onError: (Object e, StackTrace st) { if (out.isClosed) return; arm(); out.addError(e, st); }, onDone: () { t?.cancel(); if (!out.isClosed) unawaited(out.close()); }); }, onPause: () { t?.cancel(); sub?.pause(); }, onResume: () { sub?.resume(); arm(); }, onCancel: () { t?.cancel(); final x = sub; sub = null; return x?.cancel(); }, ); return out.stream; } /// 올리기: [send] 가 [data] 에서 [limit] 동안 한 조각도 더 가져가지 않으면 (서버가 받지 않아 보내기가 막힘) 또는 다 가져간 뒤 /// [limit] 안에 끝나지 않으면 (서버의 마지막 답이 없음) [RemoteIdleTimeout]. 그때 [onTimeout] 으로 그 연결을 끊는다 Future idleWrite(Stream> data, Future Function(Stream> s) send, {Duration? limit, void Function()? onTimeout}) { final d = limit ?? RemoteIdle.limit; final fail = Completer(); Timer? t; void arm() { t?.cancel(); t = Timer(d, () { if (fail.isCompleted) return; fail.completeError(RemoteIdleTimeout(d.inSeconds)); onTimeout?.call(); }); } arm(); final work = send(data.map((x) { arm(); return x; })); work.then((v) { t?.cancel(); if (!fail.isCompleted) fail.complete(v); }, onError: (Object e, StackTrace st) { t?.cancel(); if (!fail.isCompleted) fail.completeError(e, st); }); return fail.future; } /// 한 번의 요청 (SMB 조각 읽기 · 쓰기, WebDAV 응답 기다리기) 에 같은 시간 제한 Future idleCall(Future f, {Duration? limit, void Function()? onTimeout}) { final d = limit ?? RemoteIdle.limit; // 시간이 지나 연결을 끊은 뒤 원래 요청이 늦게 실패해도 (연결 닫힘 등) 처리되지 않은 오류로 남지 않게 f.ignore(); return f.timeout(d, onTimeout: () { onTimeout?.call(); throw RemoteIdleTimeout(d.inSeconds); }); }