From 65de4a8ca5a3942e96199ad1ff06285bdd9bb231 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Sat, 26 Aug 2023 23:07:16 +0800 Subject: [PATCH] #144 new: sftp `decompress` --- .dart_tool/flutter_gen/gen_l10n/l10n.dart | 6 ++ .dart_tool/flutter_gen/gen_l10n/l10n_de.dart | 3 + .dart_tool/flutter_gen/gen_l10n/l10n_en.dart | 3 + .dart_tool/flutter_gen/gen_l10n/l10n_id.dart | 3 + .dart_tool/flutter_gen/gen_l10n/l10n_zh.dart | 6 ++ lib/data/model/sftp/browser_status.dart | 1 - lib/data/res/build_data.dart | 6 +- lib/l10n/app_de.arb | 1 + lib/l10n/app_en.arb | 1 + lib/l10n/app_id.arb | 1 + lib/l10n/app_zh.arb | 1 + lib/l10n/app_zh_tw.arb | 1 + lib/view/page/storage/sftp.dart | 107 +++++++++++++++++-- macos/Runner.xcodeproj/project.pbxproj | 12 +-- 14 files changed, 133 insertions(+), 19 deletions(-) diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n.dart b/.dart_tool/flutter_gen/gen_l10n/l10n.dart index 88017151..0c4e6e73 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n.dart @@ -332,6 +332,12 @@ abstract class S { /// **'Decode'** String get decode; + /// No description provided for @decompress. + /// + /// In en, this message translates to: + /// **'Decompress'** + String get decompress; + /// No description provided for @delete. /// /// In en, this message translates to: diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart index 2de8bb38..89765f0c 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart @@ -121,6 +121,9 @@ class SDe extends S { @override String get decode => 'Decode'; + @override + String get decompress => 'Dekomprimieren'; + @override String get delete => 'Löschen'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart index ae799ecd..56f326f1 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart @@ -121,6 +121,9 @@ class SEn extends S { @override String get decode => 'Decode'; + @override + String get decompress => 'Decompress'; + @override String get delete => 'Delete'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart index f390594f..a4486207 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart @@ -121,6 +121,9 @@ class SId extends S { @override String get decode => 'Membaca sandi'; + @override + String get decompress => 'Dekompresi'; + @override String get delete => 'Menghapus'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart index d65be8f9..4f33f6d5 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart @@ -121,6 +121,9 @@ class SZh extends S { @override String get decode => '解码'; + @override + String get decompress => '解压缩'; + @override String get delete => '删除'; @@ -835,6 +838,9 @@ class SZhTw extends SZh { @override String get decode => '解碼'; + @override + String get decompress => '解壓縮'; + @override String get delete => '刪除'; diff --git a/lib/data/model/sftp/browser_status.dart b/lib/data/model/sftp/browser_status.dart index ae7d9818..bd823979 100644 --- a/lib/data/model/sftp/browser_status.dart +++ b/lib/data/model/sftp/browser_status.dart @@ -5,7 +5,6 @@ class SftpBrowserStatus { List? files; AbsolutePath? path; SftpClient? client; - bool isBusy = false; SftpBrowserStatus(); } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 5d7cb01d..0ae272e9 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,8 +2,8 @@ class BuildData { static const String name = "ServerBox"; - static const int build = 498; + static const int build = 499; static const String engine = "3.13.0"; - static const String buildAt = "2023-08-23 21:54:10.937955"; - static const int modifications = 2; + static const String buildAt = "2023-08-25 18:04:51.443337"; + static const int modifications = 4; } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 5c6fd4e5..739b1bfd 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -39,6 +39,7 @@ "dark": "Dunkel", "debug": "Debug", "decode": "Decode", + "decompress": "Dekomprimieren", "delete": "Löschen", "deleteServers": "Batch-Löschung von Servern", "disabled": "Behinderte", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 59213ed6..c157b145 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -39,6 +39,7 @@ "dark": "Dark", "debug": "Debug", "decode": "Decode", + "decompress": "Decompress", "delete": "Delete", "deleteServers": "Batch delete servers", "disabled": "Disabled", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index 9559a286..b2755420 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -39,6 +39,7 @@ "dark": "Gelap", "debug": "Debug", "decode": "Membaca sandi", + "decompress": "Dekompresi", "delete": "Menghapus", "deleteServers": "Penghapusan server secara batch", "disabled": "Dengan disabilitas", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index a668e7b9..5d8b4f50 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -39,6 +39,7 @@ "dark": "暗", "debug": "调试", "decode": "解码", + "decompress": "解压缩", "delete": "删除", "deleteServers": "批量删除服务器", "disabled": "已禁用", diff --git a/lib/l10n/app_zh_tw.arb b/lib/l10n/app_zh_tw.arb index d5c22398..80bd354b 100644 --- a/lib/l10n/app_zh_tw.arb +++ b/lib/l10n/app_zh_tw.arb @@ -39,6 +39,7 @@ "dark": "暗", "debug": "調試", "decode": "解碼", + "decompress": "解壓縮", "delete": "刪除", "deleteServers": "批量刪除服務器", "disabled": "已禁用", diff --git a/lib/view/page/storage/sftp.dart b/lib/view/page/storage/sftp.dart index 58006856..1e41c4cb 100644 --- a/lib/view/page/storage/sftp.dart +++ b/lib/view/page/storage/sftp.dart @@ -15,6 +15,7 @@ import '../../../core/extension/numx.dart'; import '../../../core/extension/stringx.dart'; import '../../../core/route.dart'; import '../../../core/utils/misc.dart'; +import '../../../core/utils/platform.dart' hide pathJoin; import '../../../core/utils/ui.dart'; import '../../../data/model/server/server_private_info.dart'; import '../../../data/model/sftp/absolute_path.dart'; @@ -130,6 +131,7 @@ class _SftpPageState extends State { _buildGotoBtn(), _buildUploadBtn(), ]; + if (isDesktop) children.add(_buildRefreshBtn()); return SafeArea( child: Container( padding: const EdgeInsets.fromLTRB(11, 7, 11, 11), @@ -256,11 +258,14 @@ class _SftpPageState extends State { ); } - Widget _buildFileView() { - if (_status.isBusy) { - return centerLoading; - } + Widget _buildRefreshBtn() { + return IconButton( + onPressed: () => _listDir(), + icon: const Icon(Icons.refresh), + ); + } + Widget _buildFileView() { if (_status.files == null) { final p_ = widget.initPath ?? '/'; _status.path = AbsolutePath(p_); @@ -341,6 +346,11 @@ class _SftpPageState extends State { title: Text(_s.download), onTap: () => _download(context, file), ), + ListTile( + leading: const Icon(Icons.folder_zip), + title: Text(_s.decompress), + onTap: () => _decompress(context, file), + ), ]); } showRoundDialog( @@ -596,6 +606,30 @@ class _SftpPageState extends State { ); } + Future _decompress(BuildContext context, SftpName name) async { + context.pop(); + final absPath = _getRemotePath(name); + final cmd = _getDecompressCmd(absPath); + if (cmd == null) { + showRoundDialog( + context: context, + title: Text(_s.error), + child: Text('Unsupport file: ${name.filename}'), + actions: [ + TextButton( + onPressed: () => context.pop(), + child: Text(_s.ok), + ), + ], + ); + return; + } + showLoadingDialog(context); + await _client?.run(cmd); + context.pop(); + _listDir(); + } + String _getRemotePath(SftpName name) { final prePath = _status.path!.path; return pathJoin(prePath, name.filename); @@ -607,10 +641,7 @@ class _SftpPageState extends State { /// Only return true if the path is changed Future _listDir({String? path, SSHClient? client}) async { - if (_status.isBusy) { - return false; - } - _status.isBusy = true; + showLoadingDialog(context); if (client != null) { final sftpc = await client.sftp(); _status.client = sftpc; @@ -637,12 +668,13 @@ class _SftpPageState extends State { if (mounted) { setState(() { _status.files = fs; - _status.isBusy = false; }); + context.pop(); return true; } return false; } catch (e, trace) { + context.pop(); _logger.warning('list dir failed', e, trace); await _backward(); Future.delayed( @@ -669,3 +701,60 @@ class _SftpPageState extends State { } } } + +String? _getDecompressCmd(String filename) { + for (final ext in _extCmdMap.keys) { + if (filename.endsWith('.$ext')) { + return _extCmdMap[ext]?.replaceAll('FILE', '"$filename"'); + } + } + return null; +} + +/// Translate from +/// https://github.com/ohmyzsh/ohmyzsh/blob/03a0d5bbaedc732436b5c67b166cde954817cc2f/plugins/extract/extract.plugin.zsh +const _extCmdMap = { + 'tar.gz': 'tar zxvf FILE', + 'tgz': 'tar zxvf FILE', + 'tar.bz2': 'tar jxvf FILE', + 'tbz2': 'tar jxvf FILE', + 'tar.xz': 'tar --xz -xvf FILE', + 'txz': 'tar --xz -xvf FILE', + 'tar.lzma': 'tar --lzma -xvf FILE', + 'tlz': 'tar --lzma -xvf FILE', + 'tar.zst': 'tar --zstd -xvf FILE', + 'tzst': 'tar --zstd -xvf FILE', + 'tar': 'tar xvf FILE', + 'tar.lz': 'tar xvf FILE', + 'tar.lz4': 'lz4 -c -d FILE | tar xvf - ', + 'gz': 'gunzip FILE', + 'bz2': 'bunzip2 FILE', + 'xz': 'unxz FILE', + 'lzma': 'unlzma FILE', + 'z': 'uncompress FILE', + 'zip': 'unzip FILE', + 'war': 'unzip FILE', + 'jar': 'unzip FILE', + 'ear': 'unzip FILE', + 'sublime-package': 'unzip FILE', + 'ipa': 'unzip FILE', + 'ipsw': 'unzip FILE', + 'apk': 'unzip FILE', + 'xpi': 'unzip FILE', + 'aar': 'unzip FILE', + 'whl': 'unzip FILE', + 'rar': 'unrar x -ad FILE', + 'rpm': 'rpm2cpio FILE | cpio --quiet -id', + '7z': '7za x FILE', + 'deb': 'mkdir -p "control" "data"' + 'ar vx FILE > /dev/null' + 'cd control; extract ../control.tar.*' + 'cd ../data; extract ../data.tar.*' + 'cd ..; rm *.tar.* debian-binary', + 'zst': 'unzstd FILE', + 'cab': 'cabextract FILE', + 'exe': 'cabextract FILE', + 'cpio': 'cpio -idmvF FILE', + 'obscpio': 'cpio -idmvF FILE', + 'zpaq': 'zpaq x FILE', +}; diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 4026af48..ea8a555c 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -474,9 +474,9 @@ baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 493; + CURRENT_PROJECT_VERSION = 499; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.493; + MARKETING_VERSION = 1.0.499; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -489,9 +489,9 @@ baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 493; + CURRENT_PROJECT_VERSION = 499; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.493; + MARKETING_VERSION = 1.0.499; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -504,9 +504,9 @@ baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 493; + CURRENT_PROJECT_VERSION = 499; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.493; + MARKETING_VERSION = 1.0.499; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0;