From 869f2c14c359b493ac39de21b281f2974e88b80e Mon Sep 17 00:00:00 2001 From: RafayAhmad7548 Date: Sat, 16 Aug 2025 10:12:51 +0500 Subject: [PATCH] make copy work, also some abstraction and new provider --- lib/providers/sftp_loading_provider.dart | 40 +++++++++ lib/{ => providers}/sftp_provider.dart | 28 ------ lib/sftp_connection_list.dart | 15 ++-- lib/sftp_explorer.dart | 108 +++++++++++------------ lib/sftp_worker.dart | 49 ++++++---- lib/widgets/operation_buttons.dart | 12 +-- 6 files changed, 145 insertions(+), 107 deletions(-) create mode 100644 lib/providers/sftp_loading_provider.dart rename lib/{ => providers}/sftp_provider.dart (57%) diff --git a/lib/providers/sftp_loading_provider.dart b/lib/providers/sftp_loading_provider.dart new file mode 100644 index 0000000..441840a --- /dev/null +++ b/lib/providers/sftp_loading_provider.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +class SftpLoadingProvider extends ChangeNotifier { + + double? _uploadProgress; + double? _downloadProgress; + + double? _copyProgress; + List? _toBeMovedOrCopied; + bool _isCopy = false; + + + double? get copyProgress => _copyProgress; + double? get uploadProgress => _uploadProgress; + double? get downloadProgress => _downloadProgress; + + List? get toBeMovedOrCopied => _toBeMovedOrCopied; + bool get isCopy => _isCopy; + + void setUploadProgress(double? progress) { + _uploadProgress = progress; + notifyListeners(); + } + + void setDownloadProgress(double? progress) { + _downloadProgress = progress; + notifyListeners(); + } + + void setCopyProgress(double? progress) { + _copyProgress = progress; + notifyListeners(); + } + + void setCopyOrMoveFiles(List? files, bool isCopy) { + _toBeMovedOrCopied = files; + _isCopy = isCopy; + notifyListeners(); + } +} diff --git a/lib/sftp_provider.dart b/lib/providers/sftp_provider.dart similarity index 57% rename from lib/sftp_provider.dart rename to lib/providers/sftp_provider.dart index e3e12fd..c4cc988 100644 --- a/lib/sftp_provider.dart +++ b/lib/providers/sftp_provider.dart @@ -9,12 +9,6 @@ class SftpProvider extends ChangeNotifier { bool _isLoading = false; late List _dirContents; - double? _uploadProgress; - double? _downloadProgress; - - List? _toBeMovedOrCopied; - bool _isCopy = false; - SftpProvider(this._sftpWorker) { listDir(); } @@ -25,12 +19,6 @@ class SftpProvider extends ChangeNotifier { bool get isLoading => _isLoading; List get dirContents => _dirContents; - double? get uploadProgress => _uploadProgress; - double? get downloadProgress => _downloadProgress; - - List? get toBeMovedOrCopied => _toBeMovedOrCopied; - bool get isCopy => _isCopy; - Future listDir() async { _isLoading = true; notifyListeners(); @@ -50,20 +38,4 @@ class SftpProvider extends ChangeNotifier { listDir(); } - void setUploadProgress(double? progress) { - _uploadProgress = progress; - notifyListeners(); - } - - void setDownloadProgress(double? progress) { - _downloadProgress = progress; - notifyListeners(); - } - - void setCopyOrMoveFiles(List? files, bool isCopy) { - _toBeMovedOrCopied = files; - _isCopy = isCopy; - notifyListeners(); - } - } diff --git a/lib/sftp_connection_list.dart b/lib/sftp_connection_list.dart index 2514458..b9efb46 100644 --- a/lib/sftp_connection_list.dart +++ b/lib/sftp_connection_list.dart @@ -3,12 +3,14 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:fluxcloud/connection.dart'; +import 'package:fluxcloud/providers/sftp_loading_provider.dart'; +import 'package:fluxcloud/providers/sftp_provider.dart'; import 'package:fluxcloud/sftp_explorer.dart'; -import 'package:fluxcloud/sftp_provider.dart'; import 'package:fluxcloud/sftp_worker.dart'; import 'package:fluxcloud/widgets/add_server_modal.dart'; import 'package:provider/provider.dart'; + class SftpConnectionList extends StatefulWidget { const SftpConnectionList({ super.key, @@ -82,10 +84,13 @@ class _SftpConnectionListState extends State { onTap: () async { final sftpWorker = await SftpWorker.spawn(_connections[index]); if (context.mounted) { - Navigator.push(context, MaterialPageRoute(builder: (context) => ChangeNotifierProvider( - create: (_) => SftpProvider(sftpWorker), - child: SftpExplorer() - ))); + Navigator.push(context, MaterialPageRoute(builder: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => SftpProvider(sftpWorker)), + ChangeNotifierProvider(create: (_) => SftpLoadingProvider()), + ], + child: SftpExplorer()) + )); } }, borderRadius: BorderRadius.circular(10), diff --git a/lib/sftp_explorer.dart b/lib/sftp_explorer.dart index 3fdc9b8..9884e54 100644 --- a/lib/sftp_explorer.dart +++ b/lib/sftp_explorer.dart @@ -1,15 +1,15 @@ import 'dart:io'; -import 'package:dartssh2/dartssh2.dart'; import 'package:file_picker/file_picker.dart'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:fluxcloud/main.dart'; -import 'package:fluxcloud/sftp_provider.dart'; +import 'package:fluxcloud/providers/sftp_loading_provider.dart'; +import 'package:fluxcloud/providers/sftp_provider.dart'; +import 'package:fluxcloud/widgets/operation_buttons.dart'; import 'package:path/path.dart'; import 'package:provider/provider.dart'; -import 'widgets/operation_buttons.dart'; class SftpExplorer extends StatelessWidget { const SftpExplorer({super.key}); @@ -34,46 +34,7 @@ class SftpExplorer extends StatelessWidget { }, icon: Icon(Icons.arrow_back) ), - actions: [ - Selector( - selector: (_, sftpProvider) => sftpProvider.uploadProgress, - builder: (_, uploadProgress, __) => uploadProgress != null ? Stack( - alignment: Alignment.center, - children: [ - TweenAnimationBuilder( - 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) - ), - ] - ) : const SizedBox.shrink(), - ), - Selector( - selector: (_, sftpProvider) => sftpProvider.downloadProgress, - builder: (_, downloadProgress, __) => 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,) - ), - IconButton( - onPressed: () { - // TODO: show donwload details here - }, - icon: Icon(Icons.download) - ), - ] - ) : const SizedBox.shrink(), - ), - ], + actions: _buildLoadingButtons, ), floatingActionButton: _buildFABs(context), bottomNavigationBar: _buildCopyMoveButton(context), @@ -127,6 +88,8 @@ class SftpExplorer extends StatelessWidget { } Widget _buildFABs(BuildContext context) { + final sftpProvider = context.read(); + final sftpLoadingProvider = context.read(); return Row( mainAxisSize: MainAxisSize.min, spacing: 10, @@ -149,7 +112,6 @@ class SftpExplorer extends StatelessWidget { TextButton(onPressed: () => Navigator.pop(context), child: Text('Cancel')), TextButton( onPressed: () async { - final sftpProvider = context.read(); try { await sftpProvider.sftpWorker.mkdir('${sftpProvider.path}${nameController.text}'); sftpProvider.listDir(); @@ -174,7 +136,6 @@ class SftpExplorer extends StatelessWidget { FloatingActionButton( heroTag: 'upload-file', onPressed: () async { - final sftpProvider = context.read(); final List filePaths; if (Platform.isAndroid | Platform.isIOS) { final res = await FilePicker.platform.pickFiles(allowMultiple: true); @@ -187,7 +148,7 @@ class SftpExplorer extends StatelessWidget { for (final filePath in filePaths) { try { await for (final progress in sftpProvider.sftpWorker.uploadFile(sftpProvider.path, filePath)) { - sftpProvider.setUploadProgress(progress); + sftpLoadingProvider.setUploadProgress(progress); } await sftpProvider.listDir(); } @@ -197,7 +158,7 @@ class SftpExplorer extends StatelessWidget { } } } - sftpProvider.setUploadProgress(null); + sftpLoadingProvider.setUploadProgress(null); sftpProvider.listDir(); }, child: Icon(Icons.upload), @@ -206,9 +167,9 @@ class SftpExplorer extends StatelessWidget { ); } - Widget _buildCopyMoveButton(BuildContext context) { - return Selector?, bool)>( - selector: (_, sftpProvider) => (sftpProvider.toBeMovedOrCopied, sftpProvider.isCopy), + Widget _buildCopyMoveButton(BuildContext context) { + return Selector?, bool)>( + selector: (_, sftpLoadingProvider) => (sftpLoadingProvider.toBeMovedOrCopied, sftpLoadingProvider.isCopy), builder: (_, data, __) { final (toBeMovedOrCopied, isCopy) = data; if (toBeMovedOrCopied == null) { @@ -222,13 +183,16 @@ class SftpExplorer extends StatelessWidget { Expanded(child: ElevatedButton( onPressed: () async { final sftpProvider = context.read(); + final sftpLoadingProvider = context.read(); for (final filePath in toBeMovedOrCopied) { try { + final fileName = basename(filePath); if (isCopy) { - + await for (final progress in sftpProvider.sftpWorker.copy(filePath, '${sftpProvider.path}$fileName')) { + sftpLoadingProvider.setCopyProgress(progress); + } } else { - final fileName = basename(filePath); await sftpProvider.sftpWorker.rename(filePath, '${sftpProvider.path}$fileName'); } } @@ -238,7 +202,9 @@ class SftpExplorer extends StatelessWidget { } } } - sftpProvider.setCopyOrMoveFiles(null, isCopy); + // TODO: figure out where to put this line + sftpLoadingProvider.setCopyOrMoveFiles(null, isCopy); + sftpLoadingProvider.setCopyProgress(null); sftpProvider.listDir(); }, style: ElevatedButton.styleFrom( @@ -252,7 +218,7 @@ class SftpExplorer extends StatelessWidget { )), IconButton( onPressed: () { - context.read().setCopyOrMoveFiles(null, isCopy); + context.read().setCopyOrMoveFiles(null, isCopy); }, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.secondaryContainer, @@ -270,4 +236,38 @@ class SftpExplorer extends StatelessWidget { ); } + List get _buildLoadingButtons => [ + Selector( + selector: (_, sftpLoadingProvider) => sftpLoadingProvider.uploadProgress, + builder: (_, uploadProgress, __) => _buildLoader(uploadProgress, Icons.upload) + ), + Selector( + selector: (_, sftpLoadingProvider) => sftpLoadingProvider.downloadProgress, + builder: (_, downloadProgress, __) => _buildLoader(downloadProgress, Icons.download) + ), + Selector( + selector: (_, sftpLoadingProvider) => sftpLoadingProvider.copyProgress, + builder: (_, copyProgress, __) => _buildLoader(copyProgress, Icons.copy) + ), + ]; + + Widget _buildLoader(double? progress, IconData icon) { + return progress != null ? Stack( + alignment: Alignment.center, + children: [ + TweenAnimationBuilder( + tween: Tween(begin: 0, end: progress), + duration: Duration(milliseconds: 300), + builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,) + ), + IconButton( + onPressed: () { + // TODO: show details here + }, + icon: Icon(icon) + ), + ] + ) : const SizedBox.shrink(); + } + } diff --git a/lib/sftp_worker.dart b/lib/sftp_worker.dart index 87697bf..72c1901 100644 --- a/lib/sftp_worker.dart +++ b/lib/sftp_worker.dart @@ -142,7 +142,7 @@ class SftpWorker { await localFileWriter.writeFrom(bytes); } } - on SftpStatusError catch (e) { + on SftpStatusError catch (e) { sendPort.send((id, RemoteError(e.message, ''))); } sendPort.send((id, 1.0)); @@ -170,7 +170,33 @@ class SftpWorker { } ); } - on SftpStatusError catch (e) { + on SftpStatusError catch (e) { + sendPort.send((id, RemoteError(e.message, ''))); + } + sendPort.send((id, 1.0)); + }).listen((_) {}); + + + final StreamController<(int, Copy)> copyController = StreamController(); + copyController.stream.asyncMap((cmd) async { + final (id, copyCmd) = cmd; + try { + final srcFile = await sftpClient.open(copyCmd.filePath); + final dstFile = await sftpClient.open(copyCmd.copyToPath, mode: SftpFileOpenMode.create | SftpFileOpenMode.write | SftpFileOpenMode.exclusive); + final fileSize = (await srcFile.stat()).size!; + bool timeout = true; + await dstFile.write( + srcFile.read(), + onProgress: (progress) { + if (timeout) { + timeout = false; + sendPort.send((id, progress/fileSize)); + Future.delayed(Duration(seconds: 2), () => timeout = true); + } + } + ); + } + on SftpStatusError catch (e) { sendPort.send((id, RemoteError(e.message, ''))); } sendPort.send((id, 1.0)); @@ -234,15 +260,8 @@ class SftpWorker { } case DownloadFile(): downloadController.add((id, command)); - case Copy(:final filePath, :final copyToPath): - try { - // TODO: complete this - sendPort.send((id, 0)); - } - on SftpStatusError catch (e) { - sendPort.send((id, RemoteError(e.message, ''))); - } - + case Copy(): + copyController.add((id, command)); } }); } @@ -325,12 +344,12 @@ class SftpWorker { return controller.stream; } - Future copy(String filePath, String copyToPath) async { - final completer = Completer.sync(); + Stream copy(String filePath, String copyToPath) { + final controller = StreamController(); final id = _idCounter++; - _activeRequests[id] = completer; + _activeRequests[id] = controller; _commands.send((id, Copy(filePath, copyToPath))); - await completer.future; + return controller.stream; } } diff --git a/lib/widgets/operation_buttons.dart b/lib/widgets/operation_buttons.dart index 82bc312..af0ed26 100644 --- a/lib/widgets/operation_buttons.dart +++ b/lib/widgets/operation_buttons.dart @@ -1,7 +1,8 @@ import 'package:dartssh2/dartssh2.dart'; import 'package:flutter/material.dart'; import 'package:fluxcloud/main.dart'; -import 'package:fluxcloud/sftp_provider.dart'; +import 'package:fluxcloud/providers/sftp_loading_provider.dart'; +import 'package:fluxcloud/providers/sftp_provider.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; @@ -15,20 +16,21 @@ class OperationButtons extends StatelessWidget { @override Widget build(BuildContext context) { final sftpProvider = context.read(); + final sftpLoadingProvider = context.read(); return Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( onPressed: () { final filePaths = dirEntries.map((dirEntry) => '${sftpProvider.path}${dirEntry.filename}').toList(); - sftpProvider.setCopyOrMoveFiles(filePaths, false); + sftpLoadingProvider.setCopyOrMoveFiles(filePaths, false); }, icon: Icon(Icons.drive_file_move) ), IconButton( onPressed: () { final filePaths = dirEntries.map((dirEntry) => '${sftpProvider.path}${dirEntry.filename}').toList(); - sftpProvider.setCopyOrMoveFiles(filePaths, true); + sftpLoadingProvider.setCopyOrMoveFiles(filePaths, true); }, icon: Icon(Icons.copy) ), @@ -125,7 +127,7 @@ class OperationButtons extends StatelessWidget { for (final dirEntry in dirEntries) { try { await for (final progress in sftpProvider.sftpWorker.downloadFile(dirEntry, sftpProvider.path, downloadsDir.path)) { - sftpProvider.setDownloadProgress(progress); + sftpLoadingProvider.setDownloadProgress(progress); } } catch (e) { @@ -134,7 +136,7 @@ class OperationButtons extends StatelessWidget { } } } - sftpProvider.setDownloadProgress(null); + sftpLoadingProvider.setDownloadProgress(null); }, icon: Icon(Icons.download) )