download worky, ui bit buggy with mutiple downlaods
This commit is contained in:
parent
3f12b14e8a
commit
33ade0479f
5 changed files with 114 additions and 15 deletions
|
@ -25,7 +25,10 @@ class _SftpExplorerState extends State<SftpExplorer> {
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
late List<SftpName> _dirContents;
|
late List<SftpName> _dirContents;
|
||||||
|
|
||||||
double? _progress;
|
double? _uploadProgress;
|
||||||
|
double? _downloadProgress;
|
||||||
|
|
||||||
|
void _setDownloadProgress(double? progress) => setState(() => _downloadProgress = progress);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -70,12 +73,29 @@ class _SftpExplorerState extends State<SftpExplorer> {
|
||||||
icon: Icon(Icons.arrow_back)
|
icon: Icon(Icons.arrow_back)
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
if (_progress != null)
|
if (_uploadProgress != null)
|
||||||
Stack(
|
Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
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<double>(
|
||||||
|
tween: Tween(begin: 0, end: _downloadProgress),
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,)
|
builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,)
|
||||||
),
|
),
|
||||||
|
@ -83,7 +103,7 @@ class _SftpExplorerState extends State<SftpExplorer> {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: show donwload details here
|
// TODO: show donwload details here
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.upload)
|
icon: Icon(Icons.download)
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
@ -125,7 +145,7 @@ class _SftpExplorerState extends State<SftpExplorer> {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Icon(dirEntry.attr.isDirectory ? Icons.folder : Icons.description),
|
leading: Icon(dirEntry.attr.isDirectory ? Icons.folder : Icons.description),
|
||||||
title: Text(dirEntry.filename),
|
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: () {
|
onTap: () {
|
||||||
if (dirEntry.attr.isDirectory) {
|
if (dirEntry.attr.isDirectory) {
|
||||||
path = '$path${dirEntry.filename}/';
|
path = '$path${dirEntry.filename}/';
|
||||||
|
@ -197,8 +217,17 @@ class _SftpExplorerState extends State<SftpExplorer> {
|
||||||
filePaths = files.map((file) => file.path).toList();
|
filePaths = files.map((file) => file.path).toList();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
bool start = true;
|
||||||
await for (final progress in widget.sftpWorker.uploadFiles(path, filePaths)) {
|
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) {
|
catch (e) {
|
||||||
|
@ -206,7 +235,7 @@ class _SftpExplorerState extends State<SftpExplorer> {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString()));
|
ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setState(() => _progress = null);
|
setState(() => _uploadProgress = null);
|
||||||
_listDir();
|
_listDir();
|
||||||
},
|
},
|
||||||
child: Icon(Icons.upload),
|
child: Icon(Icons.upload),
|
||||||
|
|
|
@ -17,9 +17,9 @@ class ListDir extends SftpCommand {
|
||||||
|
|
||||||
class UploadFiles extends SftpCommand {
|
class UploadFiles extends SftpCommand {
|
||||||
final String path;
|
final String path;
|
||||||
final List<String> fileNames;
|
final List<String> filePaths;
|
||||||
|
|
||||||
UploadFiles(this.path, this.fileNames);
|
UploadFiles(this.path, this.filePaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MkDir extends SftpCommand {
|
class MkDir extends SftpCommand {
|
||||||
|
@ -42,6 +42,14 @@ class Rename extends SftpCommand {
|
||||||
Rename(this.oldpath, this.newpath);
|
Rename(this.oldpath, this.newpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DownloadFiles extends SftpCommand {
|
||||||
|
final List<SftpName> files;
|
||||||
|
final String path;
|
||||||
|
final String downloadPath;
|
||||||
|
|
||||||
|
DownloadFiles(this.files, this.path, this.downloadPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class SftpWorker {
|
class SftpWorker {
|
||||||
|
|
||||||
|
@ -112,7 +120,7 @@ class SftpWorker {
|
||||||
on SftpStatusError catch (e) {
|
on SftpStatusError catch (e) {
|
||||||
sendPort.send((id, RemoteError(e.message, '')));
|
sendPort.send((id, RemoteError(e.message, '')));
|
||||||
}
|
}
|
||||||
case UploadFiles(:final path, fileNames:final filePaths):
|
case UploadFiles(:final path, :final filePaths):
|
||||||
for (var filePath in filePaths) {
|
for (var filePath in filePaths) {
|
||||||
try {
|
try {
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
|
@ -138,6 +146,7 @@ class SftpWorker {
|
||||||
}
|
}
|
||||||
sendPort.send((id, 1.0));
|
sendPort.send((id, 1.0));
|
||||||
}
|
}
|
||||||
|
sendPort.send((id, null));
|
||||||
case MkDir(:final path):
|
case MkDir(:final path):
|
||||||
try {
|
try {
|
||||||
await sftpClient.mkdir(path);
|
await sftpClient.mkdir(path);
|
||||||
|
@ -181,12 +190,42 @@ class SftpWorker {
|
||||||
on SftpStatusError catch (e) {
|
on SftpStatusError catch (e) {
|
||||||
sendPort.send((id, RemoteError(e.message, '')));
|
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) {
|
void _sftpResponseHandler(dynamic message) {
|
||||||
final (int id, Object response) = message;
|
final (int id, Object? response) = message;
|
||||||
|
|
||||||
if (_activeRequests[id] is Completer) {
|
if (_activeRequests[id] is Completer) {
|
||||||
final completer = _activeRequests.remove(id)!;
|
final completer = _activeRequests.remove(id)!;
|
||||||
|
@ -204,11 +243,13 @@ class SftpWorker {
|
||||||
controller.addError(response);
|
controller.addError(response);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
controller.add(response);
|
if (response == null) {
|
||||||
if (response == 1) {
|
|
||||||
controller.close();
|
controller.close();
|
||||||
_activeRequests.remove(id);
|
_activeRequests.remove(id);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
controller.add(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,4 +296,12 @@ class SftpWorker {
|
||||||
await completer.future;
|
await completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stream<double> downloadFiles(List<SftpName> files, String path, String downloadPath) {
|
||||||
|
final controller = StreamController<double>();
|
||||||
|
final id = _idCounter++;
|
||||||
|
_activeRequests[id] = controller;
|
||||||
|
_commands.send((id, DownloadFiles(files, path, downloadPath)));
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,19 @@ import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluxcloud/main.dart';
|
import 'package:fluxcloud/main.dart';
|
||||||
import 'package:fluxcloud/sftp_worker.dart';
|
import 'package:fluxcloud/sftp_worker.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
class OperationButtons extends StatelessWidget {
|
class OperationButtons extends StatelessWidget {
|
||||||
const OperationButtons({
|
const OperationButtons({
|
||||||
super.key,
|
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 SftpWorker sftpWorker;
|
||||||
final String path;
|
final String path;
|
||||||
final List<SftpName> dirEntries;
|
final List<SftpName> dirEntries;
|
||||||
final Function listDir;
|
final Function listDir;
|
||||||
|
final Function(double? progress) setDownloadProgress;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -117,6 +119,24 @@ class OperationButtons extends StatelessWidget {
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.delete)
|
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)
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,7 +329,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
|
|
@ -14,6 +14,7 @@ dependencies:
|
||||||
dartssh2: ^2.13.0
|
dartssh2: ^2.13.0
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
file_picker: ^10.2.0
|
file_picker: ^10.2.0
|
||||||
|
path_provider: ^2.1.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue