120 lines
4.1 KiB
Dart
120 lines
4.1 KiB
Dart
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<T> idleRead<T>(Stream<T> s, {Duration? limit, void Function()? onTimeout}) {
|
|
final d = limit ?? RemoteIdle.limit;
|
|
late StreamController<T> out;
|
|
StreamSubscription<T>? 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<T>(
|
|
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<T> idleWrite<T>(Stream<List<int>> data, Future<T> Function(Stream<List<int>> s) send,
|
|
{Duration? limit, void Function()? onTimeout}) {
|
|
final d = limit ?? RemoteIdle.limit;
|
|
final fail = Completer<T>();
|
|
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<T> idleCall<T>(Future<T> f, {Duration? limit, void Function()? onTimeout}) {
|
|
final d = limit ?? RemoteIdle.limit;
|
|
// 시간이 지나 연결을 끊은 뒤 원래 요청이 늦게 실패해도 (연결 닫힘 등) 처리되지 않은 오류로 남지 않게
|
|
f.ignore();
|
|
return f.timeout(d, onTimeout: () {
|
|
onTimeout?.call();
|
|
throw RemoteIdleTimeout(d.inSeconds);
|
|
});
|
|
}
|