part of 'worker.dart'; class SftpReq { final Spi spi; final String remotePath; final String localPath; final SftpReqType type; String? privateKey; List? jumpChain; List? jumpPrivateKeys; Map? knownHostFingerprints; SftpReq(this.spi, this.remotePath, this.localPath, this.type) { final keyId = spi.keyId; if (keyId != null) { privateKey = getPrivateKey(keyId); } if (spi.jumpChainIds != null || spi.jumpId != null) { final chain = []; final keys = []; final visited = {spi.id.isNotEmpty ? spi.id : spi.oldId}; final hopIds = spi.jumpChainIds ?? (spi.jumpId == null ? const [] : [spi.jumpId!]); for (final hopId in hopIds) { final hopSpi = Stores.server.box.get(hopId); if (hopSpi == null) break; final hopKey = hopSpi.id.isNotEmpty ? hopSpi.id : hopSpi.oldId; if (!visited.add(hopKey)) { throw SSHErr( type: SSHErrType.connect, message: 'Jump loop detected while building SFTP chain: ${hopSpi.name}', ); } chain.add(hopSpi); keys.add(hopSpi.keyId != null ? getPrivateKey(hopSpi.keyId!) : null); } // Always set when a jump is configured so the isolate won't fallback to Stores. jumpChain = chain; jumpPrivateKeys = keys; } try { knownHostFingerprints = Map.from(Stores.setting.sshKnownHostFingerprints.get()); } catch (e, s) { Loggers.app.warning('Failed to load SSH known host fingerprints', e, s); knownHostFingerprints = null; } } } enum SftpReqType { download, upload } 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; 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 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(); } } enum SftpWorkerStatus { preparing, sshConnected, loading, finished }