Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions lib/core/utils/sftp_timeout.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'dart:async';

import 'package:dartssh2/dartssh2.dart';
import 'package:fl_lib/fl_lib.dart';

Duration sftpOperationTimeout(int seconds) {
return Duration(seconds: seconds <= 0 ? 5 : seconds);
}

Future<T> withSftpOpTimeout<T>(
String operation,
Future<T> future,
Duration timeout,
) async {
try {
return await future.timeout(timeout);
} on TimeoutException catch (e, s) {
final error = TimeoutException('SFTP $operation timed out', timeout);
Loggers.app.warning(error.message, e, s);
throw error;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

Future<SftpClient> withSftpSessionOpenTimeout(
String operation,
Future<SftpClient> future,
Duration timeout,
) async {
try {
return await future.timeout(timeout);
} on TimeoutException catch (e, s) {
unawaited(
future.then((client) => client.close()).catchError((Object closeError) {
Loggers.app.warning('Failed to close timed out SFTP session', closeError);
}),
);
final error = TimeoutException('SFTP $operation timed out', timeout);
Loggers.app.warning(error.message, e, s);
throw error;
}
}
110 changes: 7 additions & 103 deletions lib/data/model/sftp/req.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
part of 'worker.dart';
import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/core/utils/jump_chain.dart';
import 'package:server_box/core/utils/refresh_interval.dart';
import 'package:server_box/core/utils/server.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/res/default.dart';
import 'package:server_box/data/res/store.dart';

class SftpReq {
final Spi spi;
Expand Down Expand Up @@ -87,106 +93,4 @@ class SftpTransferProgress {
});
}

class SftpReqStatus {
final int id;
final SftpReq req;
final void Function() notifyListeners;
late SftpWorker worker;
final Completer? completer;

String get fileName => req.localPath.split(Pfs.seperator).last;

// status of the download
double? progress;
double? speedBytesPerSecond;
int? transferredBytes;
DateTime? _speedSampleTime;
int _speedSampleBytes = 0;
SftpWorkerStatus? status;
int? size;
Exception? error;
Duration? spentTime;

SftpReqStatus({
required this.req,
required this.notifyListeners,
this.completer,
}) : id = DateTime.now().microsecondsSinceEpoch {
worker = SftpWorker(onNotify: onNotify, req: req)..init();
}

@override
bool operator ==(Object other) => other is SftpReqStatus && id == other.id;

@override
int get hashCode => id ^ super.hashCode;

void dispose() {
worker._dispose();
completer?.complete(true);
}

void onNotify(dynamic event) {
var shouldDispose = false;
switch (event) {
case final SftpWorkerStatus val:
status = val;
if (status == SftpWorkerStatus.finished) {
dispose();
}
break;
case final double val:
progress = val;
break;
case final SftpTransferProgress val:
progress = val.percent;
transferredBytes = val.transferredBytes;
_initSpeedSampleIfNeeded(val.transferredBytes);
break;
case final int val:
size = val;
break;
case final Duration d:
spentTime = d;
break;
default:
error = Exception('sftp worker event: $event');
Loggers.app.warning(error);
shouldDispose = true;
}
notifyListeners();
if (shouldDispose) dispose();
}

void _initSpeedSampleIfNeeded(int transferredBytes) {
if (_speedSampleTime != null) return;
_speedSampleTime = DateTime.now();
_speedSampleBytes = transferredBytes;
}

bool refreshSpeed([DateTime? now]) {
if (status != SftpWorkerStatus.loading) return false;

final sampleTime = now ?? DateTime.now();
final sampleBytes = transferredBytes ?? 0;
final lastSampleTime = _speedSampleTime;
if (lastSampleTime == null) {
_speedSampleTime = sampleTime;
_speedSampleBytes = sampleBytes;
return false;
}

final elapsedMs = sampleTime.difference(lastSampleTime).inMilliseconds;
if (elapsedMs <= 0) return false;

final speed = (sampleBytes - _speedSampleBytes) * 1000 / elapsedMs;
_speedSampleTime = sampleTime;
_speedSampleBytes = sampleBytes;

if (speedBytesPerSecond == speed) return false;
speedBytesPerSecond = speed;
return true;
}
}

enum SftpWorkerStatus { preparing, sshConnectted, loading, finished }
122 changes: 122 additions & 0 deletions lib/data/model/sftp/status.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import 'dart:async';

import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/sftp/req.dart';
import 'package:server_box/data/model/sftp/worker.dart';

class SftpReqStatus {
final int id;
final SftpReq req;
final void Function() notifyListeners;
late SftpWorker worker;
final Completer? completer;

String get fileName => req.localPath.split(Pfs.seperator).last;

// status of the download
double? progress;
double? speedBytesPerSecond;
int? transferredBytes;
DateTime? _speedSampleTime;
int _speedSampleBytes = 0;
SftpWorkerStatus? status;
int? size;
Exception? error;
Duration? spentTime;
bool _disposed = false;

SftpReqStatus({
required this.req,
required this.notifyListeners,
this.completer,
}) : id = DateTime.now().microsecondsSinceEpoch {
worker = SftpWorker(onNotify: onNotify, req: req);
unawaited(_initWorker());
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@override
bool operator ==(Object other) => other is SftpReqStatus && id == other.id;

@override
int get hashCode => id.hashCode;

void dispose() {
if (_disposed) return;
_disposed = true;
worker.dispose();
if (completer?.isCompleted == false) {
completer?.complete(true);
}
}

Future<void> _initWorker() async {
try {
await worker.init();
} catch (e, s) {
Loggers.app.warning('Failed to initialize SFTP worker', e, s);
onNotify(e);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void onNotify(dynamic event) {
var shouldDispose = false;
switch (event) {
case final SftpWorkerStatus val:
status = val;
if (status == SftpWorkerStatus.finished) {
dispose();
}
break;
case final double val:
progress = val;
break;
case final SftpTransferProgress val:
progress = val.percent;
transferredBytes = val.transferredBytes;
_initSpeedSampleIfNeeded(val.transferredBytes);
break;
case final int val:
size = val;
break;
case final Duration d:
spentTime = d;
break;
default:
error = Exception('sftp worker event: $event');
Loggers.app.warning(error);
shouldDispose = true;
}
notifyListeners();
if (shouldDispose) dispose();
}

void _initSpeedSampleIfNeeded(int transferredBytes) {
if (_speedSampleTime != null) return;
_speedSampleTime = DateTime.now();
_speedSampleBytes = transferredBytes;
}

bool refreshSpeed([DateTime? now]) {
if (status != SftpWorkerStatus.loading) return false;

final sampleTime = now ?? DateTime.now();
final sampleBytes = transferredBytes ?? 0;
final lastSampleTime = _speedSampleTime;
if (lastSampleTime == null) {
_speedSampleTime = sampleTime;
_speedSampleBytes = sampleBytes;
return false;
}

final elapsedMs = sampleTime.difference(lastSampleTime).inMilliseconds;
if (elapsedMs <= 0) return false;

final speed = (sampleBytes - _speedSampleBytes) * 1000 / elapsedMs;
_speedSampleTime = sampleTime;
_speedSampleBytes = sampleBytes;

if (speedBytesPerSecond == speed) return false;
speedBytesPerSecond = speed;
return true;
}
}
Loading