From 33ade0479f217c3601ae0fd5884738a783bd4721 Mon Sep 17 00:00:00 2001 From: RafayAhmad7548 Date: Wed, 13 Aug 2025 09:58:47 +0500 Subject: [PATCH] download worky, ui bit buggy with mutiple downlaods --- lib/sftp_explorer.dart | 43 +++++++++++++++++---- lib/sftp_worker.dart | 61 +++++++++++++++++++++++++++--- lib/widgets/operation_buttons.dart | 22 ++++++++++- pubspec.lock | 2 +- pubspec.yaml | 1 + 5 files changed, 114 insertions(+), 15 deletions(-) diff --git a/lib/sftp_explorer.dart b/lib/sftp_explorer.dart index 49f6e53..9c50dc4 100644 --- a/lib/sftp_explorer.dart +++ b/lib/sftp_explorer.dart @@ -25,7 +25,10 @@ class _SftpExplorerState extends State { bool _isLoading = true; late List _dirContents; - double? _progress; + double? _uploadProgress; + double? _downloadProgress; + + void _setDownloadProgress(double? progress) => setState(() => _downloadProgress = progress); @override void initState() { @@ -70,12 +73,29 @@ class _SftpExplorerState extends State { icon: Icon(Icons.arrow_back) ), actions: [ - if (_progress != null) + if (_uploadProgress != null) Stack( alignment: Alignment.center, children: [ TweenAnimationBuilder( - tween: Tween(begin: 0, end: _progress), + tween: Tween(begin: 0, end: _uploadProgress), + duration: Duration(milliseconds: 300), + builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,) + ), + IconButton( + onPressed: () { + // TODO: show upload details here + }, + icon: Icon(Icons.upload) + ), + ] + ), + if (_downloadProgress != null) + Stack( + alignment: Alignment.center, + children: [ + TweenAnimationBuilder( + tween: Tween(begin: 0, end: _downloadProgress), duration: Duration(milliseconds: 300), builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,) ), @@ -83,7 +103,7 @@ class _SftpExplorerState extends State { onPressed: () { // TODO: show donwload details here }, - icon: Icon(Icons.upload) + icon: Icon(Icons.download) ), ] ), @@ -125,7 +145,7 @@ class _SftpExplorerState extends State { return ListTile( leading: Icon(dirEntry.attr.isDirectory ? Icons.folder : Icons.description), title: Text(dirEntry.filename), - trailing: OperationButtons(sftpWorker: widget.sftpWorker, path: path, dirEntries: [dirEntry], listDir: _listDir,), + trailing: OperationButtons(sftpWorker: widget.sftpWorker, path: path, dirEntries: [dirEntry], listDir: _listDir, setDownloadProgress: _setDownloadProgress,), onTap: () { if (dirEntry.attr.isDirectory) { path = '$path${dirEntry.filename}/'; @@ -197,8 +217,17 @@ class _SftpExplorerState extends State { filePaths = files.map((file) => file.path).toList(); } try { + bool start = true; await for (final progress in widget.sftpWorker.uploadFiles(path, filePaths)) { - setState(() => _progress = progress); + if (start) { + start = false; + _listDir(); + } + setState(() => _uploadProgress = progress); + if (progress == 1) { + // TODO: fix: next file also starts to show + _listDir(); + } } } catch (e) { @@ -206,7 +235,7 @@ class _SftpExplorerState extends State { ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString())); } } - setState(() => _progress = null); + setState(() => _uploadProgress = null); _listDir(); }, child: Icon(Icons.upload), diff --git a/lib/sftp_worker.dart b/lib/sftp_worker.dart index fb4607c..eff00d6 100644 --- a/lib/sftp_worker.dart +++ b/lib/sftp_worker.dart @@ -17,9 +17,9 @@ class ListDir extends SftpCommand { class UploadFiles extends SftpCommand { final String path; - final List fileNames; + final List filePaths; - UploadFiles(this.path, this.fileNames); + UploadFiles(this.path, this.filePaths); } class MkDir extends SftpCommand { @@ -42,6 +42,14 @@ class Rename extends SftpCommand { Rename(this.oldpath, this.newpath); } +class DownloadFiles extends SftpCommand { + final List files; + final String path; + final String downloadPath; + + DownloadFiles(this.files, this.path, this.downloadPath); +} + class SftpWorker { @@ -112,7 +120,7 @@ class SftpWorker { on SftpStatusError catch (e) { sendPort.send((id, RemoteError(e.message, ''))); } - case UploadFiles(:final path, fileNames:final filePaths): + case UploadFiles(:final path, :final filePaths): for (var filePath in filePaths) { try { final file = File(filePath); @@ -138,6 +146,7 @@ class SftpWorker { } sendPort.send((id, 1.0)); } + sendPort.send((id, null)); case MkDir(:final path): try { await sftpClient.mkdir(path); @@ -181,12 +190,42 @@ class SftpWorker { on SftpStatusError catch (e) { sendPort.send((id, RemoteError(e.message, ''))); } + case DownloadFiles(:final files, :final path, :final downloadPath): + for (final file in files) { + try { + final localFile = File('$downloadPath/${file.filename}'); + if (await localFile.exists()) { + sendPort.send((id, RemoteError('File Already Exists', ''))); + continue; + } + final localFileWriter = await localFile.open(mode: FileMode.write); + final remoteFile = await sftpClient.open('$path${file.filename}'); + final fileSize = file.attr.size!; + bool timeout = true; + await for (final bytes in remoteFile.read( + onProgress: (progress) { + if (timeout) { + timeout = false; + sendPort.send((id, progress/fileSize)); + Future.delayed(Duration(seconds: 2), () => timeout = true); + } + } + )) { + await localFileWriter.writeFrom(bytes); + } + } + on SftpStatusError catch (e) { + sendPort.send((id, RemoteError(e.message, ''))); + } + sendPort.send((id, 1.0)); + } + sendPort.send((id, null)); } }); } void _sftpResponseHandler(dynamic message) { - final (int id, Object response) = message; + final (int id, Object? response) = message; if (_activeRequests[id] is Completer) { final completer = _activeRequests.remove(id)!; @@ -204,11 +243,13 @@ class SftpWorker { controller.addError(response); } else { - controller.add(response); - if (response == 1) { + if (response == null) { controller.close(); _activeRequests.remove(id); } + else { + controller.add(response); + } } } } @@ -255,4 +296,12 @@ class SftpWorker { await completer.future; } + Stream downloadFiles(List files, String path, String downloadPath) { + final controller = StreamController(); + final id = _idCounter++; + _activeRequests[id] = controller; + _commands.send((id, DownloadFiles(files, path, downloadPath))); + return controller.stream; + } + } diff --git a/lib/widgets/operation_buttons.dart b/lib/widgets/operation_buttons.dart index fe32b75..5b09634 100644 --- a/lib/widgets/operation_buttons.dart +++ b/lib/widgets/operation_buttons.dart @@ -2,17 +2,19 @@ import 'package:dartssh2/dartssh2.dart'; import 'package:flutter/material.dart'; import 'package:fluxcloud/main.dart'; import 'package:fluxcloud/sftp_worker.dart'; +import 'package:path_provider/path_provider.dart'; class OperationButtons extends StatelessWidget { const OperationButtons({ super.key, - required this.sftpWorker, required this.path, required this.dirEntries, required this.listDir, + required this.sftpWorker, required this.path, required this.dirEntries, required this.listDir, required this.setDownloadProgress, }); final SftpWorker sftpWorker; final String path; final List dirEntries; final Function listDir; + final Function(double? progress) setDownloadProgress; @override Widget build(BuildContext context) { @@ -117,6 +119,24 @@ class OperationButtons extends StatelessWidget { }, icon: Icon(Icons.delete) ), + IconButton( + onPressed: () async { + final downloadsDir = await getDownloadsDirectory(); + if (downloadsDir == null) return; + try { + await for (final progress in sftpWorker.downloadFiles(dirEntries, path, downloadsDir.path)) { + setDownloadProgress(progress); + } + setDownloadProgress(null); + } + catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString())); + } + } + }, + icon: Icon(Icons.download) + ) ], ); } diff --git a/pubspec.lock b/pubspec.lock index 6f43778..2a4a94f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -329,7 +329,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" diff --git a/pubspec.yaml b/pubspec.yaml index 6a0b597..6b69008 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: dartssh2: ^2.13.0 path: ^1.9.1 file_picker: ^10.2.0 + path_provider: ^2.1.5 dev_dependencies: flutter_test: