mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 15:24:35 +01:00
#144 new: sftp decompress
This commit is contained in:
@@ -332,6 +332,12 @@ abstract class S {
|
|||||||
/// **'Decode'**
|
/// **'Decode'**
|
||||||
String get decode;
|
String get decode;
|
||||||
|
|
||||||
|
/// No description provided for @decompress.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Decompress'**
|
||||||
|
String get decompress;
|
||||||
|
|
||||||
/// No description provided for @delete.
|
/// No description provided for @delete.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ class SDe extends S {
|
|||||||
@override
|
@override
|
||||||
String get decode => 'Decode';
|
String get decode => 'Decode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get decompress => 'Dekomprimieren';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get delete => 'Löschen';
|
String get delete => 'Löschen';
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ class SEn extends S {
|
|||||||
@override
|
@override
|
||||||
String get decode => 'Decode';
|
String get decode => 'Decode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get decompress => 'Decompress';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get delete => 'Delete';
|
String get delete => 'Delete';
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ class SId extends S {
|
|||||||
@override
|
@override
|
||||||
String get decode => 'Membaca sandi';
|
String get decode => 'Membaca sandi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get decompress => 'Dekompresi';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get delete => 'Menghapus';
|
String get delete => 'Menghapus';
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ class SZh extends S {
|
|||||||
@override
|
@override
|
||||||
String get decode => '解码';
|
String get decode => '解码';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get decompress => '解压缩';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get delete => '删除';
|
String get delete => '删除';
|
||||||
|
|
||||||
@@ -835,6 +838,9 @@ class SZhTw extends SZh {
|
|||||||
@override
|
@override
|
||||||
String get decode => '解碼';
|
String get decode => '解碼';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get decompress => '解壓縮';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get delete => '刪除';
|
String get delete => '刪除';
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ class SftpBrowserStatus {
|
|||||||
List<SftpName>? files;
|
List<SftpName>? files;
|
||||||
AbsolutePath? path;
|
AbsolutePath? path;
|
||||||
SftpClient? client;
|
SftpClient? client;
|
||||||
bool isBusy = false;
|
|
||||||
|
|
||||||
SftpBrowserStatus();
|
SftpBrowserStatus();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
class BuildData {
|
class BuildData {
|
||||||
static const String name = "ServerBox";
|
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 engine = "3.13.0";
|
||||||
static const String buildAt = "2023-08-23 21:54:10.937955";
|
static const String buildAt = "2023-08-25 18:04:51.443337";
|
||||||
static const int modifications = 2;
|
static const int modifications = 4;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"dark": "Dunkel",
|
"dark": "Dunkel",
|
||||||
"debug": "Debug",
|
"debug": "Debug",
|
||||||
"decode": "Decode",
|
"decode": "Decode",
|
||||||
|
"decompress": "Dekomprimieren",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"deleteServers": "Batch-Löschung von Servern",
|
"deleteServers": "Batch-Löschung von Servern",
|
||||||
"disabled": "Behinderte",
|
"disabled": "Behinderte",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
"debug": "Debug",
|
"debug": "Debug",
|
||||||
"decode": "Decode",
|
"decode": "Decode",
|
||||||
|
"decompress": "Decompress",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"deleteServers": "Batch delete servers",
|
"deleteServers": "Batch delete servers",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"dark": "Gelap",
|
"dark": "Gelap",
|
||||||
"debug": "Debug",
|
"debug": "Debug",
|
||||||
"decode": "Membaca sandi",
|
"decode": "Membaca sandi",
|
||||||
|
"decompress": "Dekompresi",
|
||||||
"delete": "Menghapus",
|
"delete": "Menghapus",
|
||||||
"deleteServers": "Penghapusan server secara batch",
|
"deleteServers": "Penghapusan server secara batch",
|
||||||
"disabled": "Dengan disabilitas",
|
"disabled": "Dengan disabilitas",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"dark": "暗",
|
"dark": "暗",
|
||||||
"debug": "调试",
|
"debug": "调试",
|
||||||
"decode": "解码",
|
"decode": "解码",
|
||||||
|
"decompress": "解压缩",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"deleteServers": "批量删除服务器",
|
"deleteServers": "批量删除服务器",
|
||||||
"disabled": "已禁用",
|
"disabled": "已禁用",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"dark": "暗",
|
"dark": "暗",
|
||||||
"debug": "調試",
|
"debug": "調試",
|
||||||
"decode": "解碼",
|
"decode": "解碼",
|
||||||
|
"decompress": "解壓縮",
|
||||||
"delete": "刪除",
|
"delete": "刪除",
|
||||||
"deleteServers": "批量刪除服務器",
|
"deleteServers": "批量刪除服務器",
|
||||||
"disabled": "已禁用",
|
"disabled": "已禁用",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import '../../../core/extension/numx.dart';
|
|||||||
import '../../../core/extension/stringx.dart';
|
import '../../../core/extension/stringx.dart';
|
||||||
import '../../../core/route.dart';
|
import '../../../core/route.dart';
|
||||||
import '../../../core/utils/misc.dart';
|
import '../../../core/utils/misc.dart';
|
||||||
|
import '../../../core/utils/platform.dart' hide pathJoin;
|
||||||
import '../../../core/utils/ui.dart';
|
import '../../../core/utils/ui.dart';
|
||||||
import '../../../data/model/server/server_private_info.dart';
|
import '../../../data/model/server/server_private_info.dart';
|
||||||
import '../../../data/model/sftp/absolute_path.dart';
|
import '../../../data/model/sftp/absolute_path.dart';
|
||||||
@@ -130,6 +131,7 @@ class _SftpPageState extends State<SftpPage> {
|
|||||||
_buildGotoBtn(),
|
_buildGotoBtn(),
|
||||||
_buildUploadBtn(),
|
_buildUploadBtn(),
|
||||||
];
|
];
|
||||||
|
if (isDesktop) children.add(_buildRefreshBtn());
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.fromLTRB(11, 7, 11, 11),
|
padding: const EdgeInsets.fromLTRB(11, 7, 11, 11),
|
||||||
@@ -256,11 +258,14 @@ class _SftpPageState extends State<SftpPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFileView() {
|
Widget _buildRefreshBtn() {
|
||||||
if (_status.isBusy) {
|
return IconButton(
|
||||||
return centerLoading;
|
onPressed: () => _listDir(),
|
||||||
}
|
icon: const Icon(Icons.refresh),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFileView() {
|
||||||
if (_status.files == null) {
|
if (_status.files == null) {
|
||||||
final p_ = widget.initPath ?? '/';
|
final p_ = widget.initPath ?? '/';
|
||||||
_status.path = AbsolutePath(p_);
|
_status.path = AbsolutePath(p_);
|
||||||
@@ -341,6 +346,11 @@ class _SftpPageState extends State<SftpPage> {
|
|||||||
title: Text(_s.download),
|
title: Text(_s.download),
|
||||||
onTap: () => _download(context, file),
|
onTap: () => _download(context, file),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.folder_zip),
|
||||||
|
title: Text(_s.decompress),
|
||||||
|
onTap: () => _decompress(context, file),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
showRoundDialog(
|
showRoundDialog(
|
||||||
@@ -596,6 +606,30 @@ class _SftpPageState extends State<SftpPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _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) {
|
String _getRemotePath(SftpName name) {
|
||||||
final prePath = _status.path!.path;
|
final prePath = _status.path!.path;
|
||||||
return pathJoin(prePath, name.filename);
|
return pathJoin(prePath, name.filename);
|
||||||
@@ -607,10 +641,7 @@ class _SftpPageState extends State<SftpPage> {
|
|||||||
|
|
||||||
/// Only return true if the path is changed
|
/// Only return true if the path is changed
|
||||||
Future<bool> _listDir({String? path, SSHClient? client}) async {
|
Future<bool> _listDir({String? path, SSHClient? client}) async {
|
||||||
if (_status.isBusy) {
|
showLoadingDialog(context);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_status.isBusy = true;
|
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
final sftpc = await client.sftp();
|
final sftpc = await client.sftp();
|
||||||
_status.client = sftpc;
|
_status.client = sftpc;
|
||||||
@@ -637,12 +668,13 @@ class _SftpPageState extends State<SftpPage> {
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_status.files = fs;
|
_status.files = fs;
|
||||||
_status.isBusy = false;
|
|
||||||
});
|
});
|
||||||
|
context.pop();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch (e, trace) {
|
} catch (e, trace) {
|
||||||
|
context.pop();
|
||||||
_logger.warning('list dir failed', e, trace);
|
_logger.warning('list dir failed', e, trace);
|
||||||
await _backward();
|
await _backward();
|
||||||
Future.delayed(
|
Future.delayed(
|
||||||
@@ -669,3 +701,60 @@ class _SftpPageState extends State<SftpPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
};
|
||||||
|
|||||||
@@ -474,9 +474,9 @@
|
|||||||
baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */;
|
baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 493;
|
CURRENT_PROJECT_VERSION = 499;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0.493;
|
MARKETING_VERSION = 1.0.499;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -489,9 +489,9 @@
|
|||||||
baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */;
|
baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 493;
|
CURRENT_PROJECT_VERSION = 499;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0.493;
|
MARKETING_VERSION = 1.0.499;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -504,9 +504,9 @@
|
|||||||
baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */;
|
baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 493;
|
CURRENT_PROJECT_VERSION = 499;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0.493;
|
MARKETING_VERSION = 1.0.499;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
Reference in New Issue
Block a user