From ac15864cbb2cb5078ada38d462acfec2cdedbba5 Mon Sep 17 00:00:00 2001 From: Billy TAING Date: Sun, 11 Apr 2021 14:17:04 +0200 Subject: [PATCH 1/3] feat(update): sounds null-safety, so it's cool --- .fvm/fvm_config.json | 3 + .gitignore | 3 +- .vscode/settings.json | 3 + analysis_options.yaml | 16 +++ example/lib/main.dart | 112 ++++++++++---------- example/pubspec.yaml | 54 +--------- example/test/widget_test.dart | 25 ----- lib/ssh.dart | 194 +++++++++++++++++----------------- pubspec.yaml | 44 +------- 9 files changed, 187 insertions(+), 267 deletions(-) create mode 100644 .fvm/fvm_config.json create mode 100644 .vscode/settings.json create mode 100644 analysis_options.yaml delete mode 100644 example/test/widget_test.dart diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json new file mode 100644 index 0000000..2b1ac22 --- /dev/null +++ b/.fvm/fvm_config.json @@ -0,0 +1,3 @@ +{ + "flutterSdkVersion": "2.0.4" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index b7d6206..a979f4f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ pubspec.lock build/ -.idea/ \ No newline at end of file +.idea/ +.fvm/flutter_sdk \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..138fb29 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dart.flutterSdkPath": "F:\\Documents\\DartProjects\\flutter_ssh\\.fvm\\flutter_sdk" +} \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..c96d41f --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,16 @@ +include: package:lint/analysis_options.yaml + +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + exclude: + - "**/*.g.dart" + - lib/localizations.g.dart + +linter: + rules: + camel_case_types: true + avoid_print: true + sort_constructors_first: true + avoid_classes_with_only_static_members: false \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index eab2906..b88ea12 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,50 +2,53 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; -import 'package:ssh/ssh.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:ssh/ssh.dart'; -void main() => runApp(new MyApp()); +void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override - _MyAppState createState() => new _MyAppState(); + _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { String _result = ''; - List _array; + List>? _array; Future onClickCmd() async { - var client = new SSHClient( + final client = SSHClient( host: "my.sshtest", port: 22, username: "sha", passwordOrKey: "Password01.", ); - String result; + String? result; try { result = await client.connect(); - if (result == "session_connected") result = await client.execute("ps"); + if (result == "session_connected") { + result = await client.execute("ps"); + } client.disconnect(); } on PlatformException catch (e) { print('Error: ${e.code}\nError Message: ${e.message}'); } setState(() { - _result = result; + _result = result ?? ''; _array = null; }); } Future onClickShell() async { - var client = new SSHClient( + final client = SSHClient( host: "my.sshtest", port: 22, username: "sha", passwordOrKey: { - "privateKey": """-----BEGIN RSA PRIVATE KEY----- + "privateKey": """ +-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA2DdFSeWG8wOHddRpOhf4FRqksJITr59iXdNrXq+n79QFN1g4 bvRG9zCDmyLb8EF+gah78dpJsGZVIltmfYWpsk7ok9GT/foCB1d2E6DbEU6mBIPe OLxYOqyiea8mi7iGt9BvAB4Mj+v2LnhK4O2BB6PTU4KLjSgMdqtV/EGctLdK+JEU @@ -71,7 +74,8 @@ VYm6XcNwPF/t5SM01ZuxH9NE2HZJ1cHcUGYQcUUJuqSkzsVK9j32E/akW9Cg3LVD D/fESTECgYBwWv9yveto6pP6/xbR9k/Jdgr+vXQ3BJVU3BOsD38SeSrZfMSNGqgx eiukCOIsRHYY7Qqi2vCJ62mwbHJ3RhSKKxcGpgzGX7KoGZS+bb5wb7RGNYK/mVaI pFkz72+8eA2cnbWUqHt9WqMUgUBYZTMESzQrTf7+q+0gWf49AZJ/QQ== ------END RSA PRIVATE KEY-----""", +-----END RSA PRIVATE KEY----- +""", }, ); @@ -81,22 +85,23 @@ pFkz72+8eA2cnbWUqHt9WqMUgUBYZTMESzQrTf7+q+0gWf49AZJ/QQ== }); try { - String result = await client.connect(); + String? result = await client.connect(); if (result == "session_connected") { result = await client.startShell( ptyType: "xterm", callback: (dynamic res) { setState(() { - _result += res; + _result += res as String; }); }); if (result == "shell_started") { print(await client.writeToShell("echo hello > world\n")); print(await client.writeToShell("cat world\n")); - new Future.delayed( + + Future.delayed( const Duration(seconds: 5), - () async => await client.closeShell(), + client.closeShell, ); } } @@ -106,7 +111,7 @@ pFkz72+8eA2cnbWUqHt9WqMUgUBYZTMESzQrTf7+q+0gWf49AZJ/QQ== } Future onClickSFTP() async { - var client = new SSHClient( + final client = SSHClient( host: "my.sshtest", port: 22, username: "sha", @@ -114,13 +119,13 @@ pFkz72+8eA2cnbWUqHt9WqMUgUBYZTMESzQrTf7+q+0gWf49AZJ/QQ== ); try { - String result = await client.connect(); + String? result = await client.connect(); if (result == "session_connected") { result = await client.connectSFTP(); if (result == "sftp_connected") { - var array = await client.sftpLs(); + final array = await client.sftpLs(); setState(() { - _result = result; + _result = result!; _array = array; }); @@ -131,12 +136,12 @@ pFkz72+8eA2cnbWUqHt9WqMUgUBYZTMESzQrTf7+q+0gWf49AZJ/QQ== )); print(await client.sftpRmdir("testsftprename")); - Directory tempDir = await getTemporaryDirectory(); - String tempPath = tempDir.path; - var filePath = await client.sftpDownload( + final Directory tempDir = await getTemporaryDirectory(); + final String tempPath = tempDir.path; + final filePath = await client.sftpDownload( path: "testupload", toPath: tempPath, - callback: (progress) async { + callback: (dynamic progress) async { print(progress); // if (progress == 20) await client.sftpCancelDownload(); }, @@ -144,14 +149,16 @@ pFkz72+8eA2cnbWUqHt9WqMUgUBYZTMESzQrTf7+q+0gWf49AZJ/QQ== print(await client.sftpRm("testupload")); - print(await client.sftpUpload( - path: filePath, - toPath: ".", - callback: (progress) async { - print(progress); - // if (progress == 30) await client.sftpCancelUpload(); - }, - )); + if (filePath != null) { + print(await client.sftpUpload( + path: filePath, + toPath: ".", + callback: (dynamic progress) async { + print(progress); + // if (progress == 30) await client.sftpCancelUpload(); + }, + )); + } print(await client.disconnectSFTP()); @@ -167,32 +174,29 @@ pFkz72+8eA2cnbWUqHt9WqMUgUBYZTMESzQrTf7+q+0gWf49AZJ/QQ== Widget build(BuildContext context) { Widget renderButtons() { return ButtonTheme( - padding: EdgeInsets.all(5.0), + padding: const EdgeInsets.all(5.0), child: ButtonBar( children: [ - FlatButton( - child: Text( + TextButton( + onPressed: onClickCmd, + child: const Text( 'Test command', style: TextStyle(color: Colors.white), ), - onPressed: onClickCmd, - color: Colors.blue, ), - FlatButton( - child: Text( + TextButton( + onPressed: onClickShell, + child: const Text( 'Test shell', style: TextStyle(color: Colors.white), ), - onPressed: onClickShell, - color: Colors.blue, ), - FlatButton( - child: Text( + TextButton( + onPressed: onClickSFTP, + child: const Text( 'Test SFTP', style: TextStyle(color: Colors.white), ), - onPressed: onClickSFTP, - color: Colors.blue, ), ], ), @@ -206,20 +210,18 @@ pFkz72+8eA2cnbWUqHt9WqMUgUBYZTMESzQrTf7+q+0gWf49AZJ/QQ== ), body: ListView( shrinkWrap: true, - padding: EdgeInsets.all(15.0), + padding: const EdgeInsets.all(15.0), children: [ - Text( - "Please edit the connection setting in the source code before clicking the test buttons"), + const Text("Please edit the connection setting in the source code before clicking the test buttons"), renderButtons(), Text(_result), - _array != null && _array.length > 0 - ? Column( - children: _array.map((f) { - return Text( - "${f["filename"]} ${f["isDirectory"]} ${f["modificationDate"]} ${f["lastAccess"]} ${f["fileSize"]} ${f["ownerUserID"]} ${f["ownerGroupID"]} ${f["permissions"]} ${f["flags"]}"); - }).toList(), - ) - : Container(), + if (_array != null && _array!.isNotEmpty) + Column( + children: _array!.map((Map f) { + return Text( + "${f["filename"]} ${f["isDirectory"]} ${f["modificationDate"]} ${f["lastAccess"]} ${f["fileSize"]} ${f["ownerUserID"]} ${f["ownerGroupID"]} ${f["permissions"]} ${f["flags"]}"); + }).toList(), + ), ], ), ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 162e3e8..dcd0a60 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,72 +1,24 @@ name: ssh_example description: Demonstrates how to use the ssh plugin. -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# Read more about versioning at semver.org. version: 1.0.0+1 environment: - sdk: ">=2.0.0-dev.68.0 <3.0.0" + sdk: ">=2.12.0" dependencies: + cupertino_icons: ^1.0.1 flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 - dev_dependencies: flutter_test: sdk: flutter - + path_provider: ^2.0.1 ssh: path: ../ - path_provider: ^0.4.1 -# For information on the generic Dart part of this file, see the -# following page: https://www.dartlang.org/tools/pub/pubspec -# The following section is specific to Flutter. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.io/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.io/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.io/custom-fonts/#from-packages diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index 1337bec..0000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1,25 +0,0 @@ -// This is a basic Flutter widget test. -// To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter -// provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to -// find child widgets in the widget tree, read text, and verify that the values of widget properties -// are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:ssh_example/main.dart'; - -void main() { - testWidgets('Verify Platform version', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(new MyApp()); - - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => - widget is Text && widget.data.startsWith('Running on:'), - ), - findsOneWidget); - }); -} diff --git a/lib/ssh.dart b/lib/ssh.dart index 8aa73b5..bd2f376 100644 --- a/lib/ssh.dart +++ b/lib/ssh.dart @@ -1,198 +1,196 @@ import 'dart:async'; -import 'package:meta/meta.dart'; + import 'package:flutter/services.dart'; import 'package:uuid/uuid.dart'; -const MethodChannel _channel = const MethodChannel('ssh'); -const EventChannel _eventChannel = const EventChannel('shell_sftp'); -Stream _onStateChanged; - -Stream get onStateChanged { - if (_onStateChanged == null) { - _onStateChanged = - _eventChannel.receiveBroadcastStream().map((dynamic event) => event); - } - return _onStateChanged; -} +const MethodChannel _channel = MethodChannel('ssh'); +const EventChannel _eventChannel = EventChannel('shell_sftp'); +Stream? get onStateChanged => _eventChannel.receiveBroadcastStream(); -typedef void Callback(dynamic result); +typedef Callback = void Function(dynamic result); class SSHClient { - String id; - String host; - int port; - String username; - dynamic passwordOrKey; - StreamSubscription stateSubscription; - Callback shellCallback; - Callback uploadCallback; - Callback downloadCallback; - SSHClient({ - @required this.host, - @required this.port, - @required this.username, - @required - this.passwordOrKey, // password or {privateKey: value, [publicKey: value, passphrase: value]} - }) { - var uuid = new Uuid(); - id = uuid.v4(); - stateSubscription = onStateChanged.listen((dynamic result) { - _parseOutput(result); - }); - } - - _parseOutput(dynamic result) { + required this.host, + required this.port, + required this.username, + required this.passwordOrKey, // password or {privateKey: value, [publicKey: value, passphrase: value]} + }) : id = const Uuid().v4(), + shellCallback = null, + uploadCallback = null, + downloadCallback = null, + stateSubscription = null { + stateSubscription = onStateChanged?.listen((dynamic result) { + _parseOutput(result as Map); + }); + } + + final String id; + final String host; + final int port; + final String username; + final dynamic passwordOrKey; + StreamSubscription? stateSubscription; + Callback? shellCallback; + Callback? uploadCallback; + Callback? downloadCallback; + + void _parseOutput(Map result) { switch (result["name"]) { case "Shell": - if (shellCallback != null && result["key"] == id) - shellCallback(result["value"]); + if (shellCallback != null && result["key"] == id) shellCallback!(result["value"]); break; case "DownloadProgress": - if (downloadCallback != null && result["key"] == id) - downloadCallback(result["value"]); + if (downloadCallback != null && result["key"] == id) downloadCallback!(result["value"]); break; case "UploadProgress": - if (uploadCallback != null && result["key"] == id) - uploadCallback(result["value"]); + if (uploadCallback != null && result["key"] == id) uploadCallback!(result["value"]); break; } } - Future connect() async { - var result = await _channel.invokeMethod('connectToHost', { + Future connect() async { + final result = await _channel.invokeMethod('connectToHost', { "id": id, "host": host, "port": port, "username": username, "passwordOrKey": passwordOrKey, }); + return result; } - Future execute(String cmd) async { - var result = await _channel.invokeMethod('execute', { + Future execute(String cmd) async { + final result = await _channel.invokeMethod('execute', { "id": id, "cmd": cmd, }); + return result; } - - Future portForwardL(int rport, int lport, String rhost) async { - var result = await _channel.invokeMethod('portForwardL', { + + Future portForwardL(int rport, int lport, String rhost) async { + final result = await _channel.invokeMethod('portForwardL', { "id": id, "rhost": rhost, "rport": rport, - "lport": lport + "lport": lport, }); return result; } - Future startShell({ + Future startShell({ String ptyType = "vanilla", // vanilla, vt100, vt102, vt220, ansi, xterm - Callback callback, + required Callback callback, }) async { shellCallback = callback; - var result = await _channel.invokeMethod('startShell', { + + final result = await _channel.invokeMethod('startShell', { "id": id, "ptyType": ptyType, }); + return result; } - Future writeToShell(String cmd) async { - var result = await _channel.invokeMethod('writeToShell', { + Future writeToShell(String cmd) async { + final result = await _channel.invokeMethod('writeToShell', { "id": id, "cmd": cmd, }); + return result; } - Future closeShell() async { + Future closeShell() { shellCallback = null; - await _channel.invokeMethod('closeShell', { + return _channel.invokeMethod('closeShell', { "id": id, }); } - Future connectSFTP() async { - var result = await _channel.invokeMethod('connectSFTP', { + Future connectSFTP() async { + final result = await _channel.invokeMethod('connectSFTP', { "id": id, }); return result; } - Future sftpLs([String path = '.']) async { - var result = await _channel.invokeMethod('sftpLs', { + Future>?> sftpLs([String path = '.']) async { + final result = await _channel.invokeMethod>>('sftpLs', { "id": id, "path": path, }); + return result; } - Future sftpRename({ - @required String oldPath, - @required String newPath, - }) async { - var result = await _channel.invokeMethod('sftpRename', { + Future sftpRename({required String oldPath, required String newPath}) async { + final result = await _channel.invokeMethod('sftpRename', { "id": id, "oldPath": oldPath, "newPath": newPath, }); + return result; } - Future sftpMkdir(String path) async { - var result = await _channel.invokeMethod('sftpMkdir', { + Future sftpMkdir(String path) async { + final result = await _channel.invokeMethod('sftpMkdir', { "id": id, "path": path, }); + return result; } - Future sftpRm(String path) async { - var result = await _channel.invokeMethod('sftpRm', { + Future sftpRm(String path) async { + final result = await _channel.invokeMethod('sftpRm', { "id": id, "path": path, }); + return result; } - Future sftpRmdir(String path) async { - var result = await _channel.invokeMethod('sftpRmdir', { + Future sftpRmdir(String path) async { + final result = await _channel.invokeMethod('sftpRmdir', { "id": id, "path": path, }); + return result; } - Future sftpDownload({ - @required String path, - @required String toPath, - Callback callback, + Future sftpDownload({ + required String path, + required String toPath, + Callback? callback, }) async { downloadCallback = callback; - var result = await _channel.invokeMethod('sftpDownload', { + final result = await _channel.invokeMethod('sftpDownload', { "id": id, "path": path, "toPath": toPath, }); + return result; } - Future sftpCancelDownload() async { - await _channel.invokeMethod('sftpCancelDownload', { + Future sftpCancelDownload() { + return _channel.invokeMethod('sftpCancelDownload', { "id": id, }); } - Future sftpUpload({ - @required String path, - @required String toPath, - Callback callback, + Future sftpUpload({ + required String path, + required String toPath, + Callback? callback, }) async { uploadCallback = callback; - var result = await _channel.invokeMethod('sftpUpload', { + final result = await _channel.invokeMethod('sftpUpload', { "id": id, "path": path, "toPath": toPath, @@ -200,36 +198,40 @@ class SSHClient { return result; } - Future sftpCancelUpload() async { - await _channel.invokeMethod('sftpCancelUpload', { + Future sftpCancelUpload() { + return _channel.invokeMethod('sftpCancelUpload', { "id": id, }); } - Future disconnectSFTP() async { + Future disconnectSFTP() { uploadCallback = null; downloadCallback = null; - await _channel.invokeMethod('disconnectSFTP', { + return _channel.invokeMethod('disconnectSFTP', { "id": id, }); } - disconnect() { + void disconnect() { shellCallback = null; uploadCallback = null; downloadCallback = null; - stateSubscription.cancel(); - _channel.invokeMethod('disconnect', { + stateSubscription?.cancel(); + _channel.invokeMethod('disconnect', { "id": id, }); } - - Future isConnected() async { + + Future isConnected() async { bool connected = false; // default to false - var result = await _channel.invokeMethod('isConnected', {"id": id,}); - if (result == "true") { // results returns a string, therefor we need to check the string 'true' + final result = await _channel.invokeMethod('isConnected', { + "id": id, + }); + + if (result == "true") { connected = true; } + return connected; } } diff --git a/pubspec.yaml b/pubspec.yaml index d6ed479..9c59fec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,20 +4,17 @@ version: 0.0.7 homepage: https://github.com/shaqian/flutter_ssh environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + sdk: ">=2.12.0 <3.0.0" + flutter: ">=2.0.0" dependencies: flutter: sdk: flutter + lint: ^1.5.3 + meta: ^1.3.0 + uuid: ^3.0.4 - meta: ^1.1.8 - uuid: ^2.0.0 -# For information on the generic Dart part of this file, see the -# following page: https://www.dartlang.org/tools/pub/pubspec - -# The following section is specific to Flutter. flutter: plugin: platforms: @@ -26,34 +23,3 @@ flutter: pluginClass: SshPlugin ios: pluginClass: SshPlugin - - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.io/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.io/assets-and-images/#resolution-aware. - - # To add custom fonts to your plugin package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.io/custom-fonts/#from-packages From 670aa318f613f560d02b217e6f8d7b2d59739d5c Mon Sep 17 00:00:00 2001 From: Billy TAING Date: Sun, 11 Apr 2021 21:03:53 +0200 Subject: [PATCH 2/3] fix(type): remove strong callback type --- example/lib/main.dart | 4 ++-- lib/ssh.dart | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index b88ea12..019fbe6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -14,7 +14,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { String _result = ''; - List>? _array; + List? _array; Future onClickCmd() async { final client = SSHClient( @@ -217,7 +217,7 @@ pFkz72+8eA2cnbWUqHt9WqMUgUBYZTMESzQrTf7+q+0gWf49AZJ/QQ== Text(_result), if (_array != null && _array!.isNotEmpty) Column( - children: _array!.map((Map f) { + children: _array!.map((dynamic f) { return Text( "${f["filename"]} ${f["isDirectory"]} ${f["modificationDate"]} ${f["lastAccess"]} ${f["fileSize"]} ${f["ownerUserID"]} ${f["ownerGroupID"]} ${f["permissions"]} ${f["flags"]}"); }).toList(), diff --git a/lib/ssh.dart b/lib/ssh.dart index bd2f376..d74d04f 100644 --- a/lib/ssh.dart +++ b/lib/ssh.dart @@ -21,7 +21,7 @@ class SSHClient { downloadCallback = null, stateSubscription = null { stateSubscription = onStateChanged?.listen((dynamic result) { - _parseOutput(result as Map); + _parseOutput(result); }); } @@ -35,7 +35,7 @@ class SSHClient { Callback? uploadCallback; Callback? downloadCallback; - void _parseOutput(Map result) { + void _parseOutput(dynamic result) { switch (result["name"]) { case "Shell": if (shellCallback != null && result["key"] == id) shellCallback!(result["value"]); @@ -117,8 +117,8 @@ class SSHClient { return result; } - Future>?> sftpLs([String path = '.']) async { - final result = await _channel.invokeMethod>>('sftpLs', { + Future?> sftpLs([String path = '.']) async { + final result = await _channel.invokeMethod>('sftpLs', { "id": id, "path": path, }); From bc61d608866393ff26b1b10600d64248047a49e5 Mon Sep 17 00:00:00 2001 From: Billy TAING Date: Sun, 11 Apr 2021 21:32:48 +0200 Subject: [PATCH 3/3] feat(dto): add dto for sftp ls --- README.md | 13 +++++++++++++ lib/model.dart | 31 +++++++++++++++++++++++++++++++ lib/model.g.dart | 34 ++++++++++++++++++++++++++++++++++ lib/ssh.dart | 16 +++++++++++++--- pubspec.yaml | 5 +++++ 5 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 lib/model.dart create mode 100644 lib/model.g.dart diff --git a/README.md b/README.md index 6403207..8853ca7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![style: lint](https://img.shields.io/badge/style-lint-4BC0F5.svg)](https://pub.dev/packages/lint) + # ssh SSH and SFTP client for Flutter. Wraps iOS library [NMSSH](https://github.com/NMSSH/NMSSH) and Android library [JSch](http://www.jcraft.com/jsch/). @@ -6,6 +8,14 @@ SSH and SFTP client for Flutter. Wraps iOS library [NMSSH](https://github.com/NM Add `ssh` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). +## Contribute + +### Generate DTO + +``` +fvm flutter pub run build_runner build +``` + ## Known issue - Platform exception in release mode for Android: @@ -109,6 +119,9 @@ await client.closeShell(); ### SFTP #### Connect SFTP: + +Make sure to call connect method before connectSFTP + ```dart await client.connectSFTP(); ``` diff --git a/lib/model.dart b/lib/model.dart new file mode 100644 index 0000000..18223b8 --- /dev/null +++ b/lib/model.dart @@ -0,0 +1,31 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'model.g.dart'; + +@JsonSerializable() +class SFTPLsData { + const SFTPLsData({ + required this.filename, + required this.fileSize, + required this.isDirectory, + required this.permissions, + required this.ownerGroupID, + required this.ownerUserID, + required this.modificationDate, + required this.lastAccess, + required this.flags, + }); + + factory SFTPLsData.fromJson(Map json) => _$SFTPLsDataFromJson(json); + Map toJson() => _$SFTPLsDataToJson(this); + + final String filename; + final int fileSize; + final bool isDirectory; + final String permissions; + final int ownerGroupID; + final int ownerUserID; + final DateTime modificationDate; + final DateTime lastAccess; + final int flags; +} diff --git a/lib/model.g.dart b/lib/model.g.dart new file mode 100644 index 0000000..1db9c6c --- /dev/null +++ b/lib/model.g.dart @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SFTPLsData _$SFTPLsDataFromJson(Map json) { + return SFTPLsData( + filename: json['filename'] as String, + fileSize: json['fileSize'] as int, + isDirectory: json['isDirectory'] as bool, + permissions: json['permissions'] as String, + ownerGroupID: json['ownerGroupID'] as int, + ownerUserID: json['ownerUserID'] as int, + modificationDate: DateTime.parse(json['modificationDate'] as String), + lastAccess: DateTime.parse(json['lastAccess'] as String), + flags: json['flags'] as int, + ); +} + +Map _$SFTPLsDataToJson(SFTPLsData instance) => + { + 'filename': instance.filename, + 'fileSize': instance.fileSize, + 'isDirectory': instance.isDirectory, + 'permissions': instance.permissions, + 'ownerGroupID': instance.ownerGroupID, + 'ownerUserID': instance.ownerUserID, + 'modificationDate': instance.modificationDate.toIso8601String(), + 'lastAccess': instance.lastAccess.toIso8601String(), + 'flags': instance.flags, + }; diff --git a/lib/ssh.dart b/lib/ssh.dart index d74d04f..9e30b92 100644 --- a/lib/ssh.dart +++ b/lib/ssh.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; +import 'package:ssh/model.dart'; import 'package:uuid/uuid.dart'; const MethodChannel _channel = MethodChannel('ssh'); @@ -117,13 +118,22 @@ class SSHClient { return result; } - Future?> sftpLs([String path = '.']) async { - final result = await _channel.invokeMethod>('sftpLs', { + Future?> sftpLs([String path = '.']) async { + final rawResult = await _channel.invokeMethod>('sftpLs', { "id": id, "path": path, }); - return result; + if (rawResult == null) return null; + + // convert from Map to Map + return rawResult + .map((dynamic r) { + return (r as Map) + .map((dynamic key, dynamic value) => MapEntry(key.toString(), value)); + }) + .map((e) => SFTPLsData.fromJson(e)) + .toList(); } Future sftpRename({required String oldPath, required String newPath}) async { diff --git a/pubspec.yaml b/pubspec.yaml index 9c59fec..62c6538 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,10 +10,15 @@ environment: dependencies: flutter: sdk: flutter + json_annotation: ^4.0.1 lint: ^1.5.3 meta: ^1.3.0 uuid: ^3.0.4 +dev_dependencies: + build_runner: ^1.12.2 + json_serializable: ^4.1.0 + flutter: plugin: