diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n.dart b/.dart_tool/flutter_gen/gen_l10n/l10n.dart index 0c4e6e73..953f2c33 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n.dart @@ -416,12 +416,6 @@ abstract class S { /// **'Download'** String get download; - /// No description provided for @downloadStatus. - /// - /// In en, this message translates to: - /// **'{percent}% of {size}'** - String downloadStatus(Object percent, Object size); - /// No description provided for @edit. /// /// In en, this message translates to: @@ -920,6 +914,12 @@ abstract class S { /// **'Path'** String get path; + /// No description provided for @percentOfSize. + /// + /// In en, this message translates to: + /// **'{percent}% of {size}'** + String percentOfSize(Object percent, Object size); + /// No description provided for @pickFile. /// /// In en, this message translates to: @@ -1154,6 +1154,12 @@ abstract class S { /// **'Preparing to connect...'** String get sftpDlPrepare; + /// No description provided for @sftpRmrfDir. + /// + /// In en, this message translates to: + /// **'Use `rm -rf` to delete dir on SFTP'** + String get sftpRmrfDir; + /// No description provided for @sftpSSHConnected. /// /// 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 89765f0c..c58120d6 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart @@ -171,11 +171,6 @@ class SDe extends S { @override String get download => 'Download'; - @override - String downloadStatus(Object percent, Object size) { - return '$percent% von $size'; - } - @override String get edit => 'Bearbeiten'; @@ -437,6 +432,11 @@ class SDe extends S { @override String get path => 'Pfad'; + @override + String percentOfSize(Object percent, Object size) { + return '$percent% von $size'; + } + @override String get pickFile => 'Datei wählen'; @@ -558,6 +558,9 @@ class SDe extends S { @override String get sftpDlPrepare => 'Verbindung vorbereiten...'; + @override + String get sftpRmrfDir => 'Verwenden Sie `rm -rf`, um das Verzeichnis auf SFTP zu löschen'; + @override String get sftpSSHConnected => 'SFTP Verbunden'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart index 56f326f1..7645e0af 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart @@ -171,11 +171,6 @@ class SEn extends S { @override String get download => 'Download'; - @override - String downloadStatus(Object percent, Object size) { - return '$percent% of $size'; - } - @override String get edit => 'Edit'; @@ -437,6 +432,11 @@ class SEn extends S { @override String get path => 'Path'; + @override + String percentOfSize(Object percent, Object size) { + return '$percent% of $size'; + } + @override String get pickFile => 'Pick file'; @@ -558,6 +558,9 @@ class SEn extends S { @override String get sftpDlPrepare => 'Preparing to connect...'; + @override + String get sftpRmrfDir => 'Use `rm -rf` to delete dir on SFTP'; + @override String get sftpSSHConnected => 'SFTP Connected'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart index a4486207..e9e3e0fe 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart @@ -171,11 +171,6 @@ class SId extends S { @override String get download => 'Unduh'; - @override - String downloadStatus(Object percent, Object size) { - return '$percent% dari $size'; - } - @override String get edit => 'Edit'; @@ -437,6 +432,11 @@ class SId extends S { @override String get path => 'Jalur'; + @override + String percentOfSize(Object percent, Object size) { + return '$percent% dari $size'; + } + @override String get pickFile => 'Pilih file'; @@ -558,6 +558,9 @@ class SId extends S { @override String get sftpDlPrepare => 'Bersiap untuk terhubung ...'; + @override + String get sftpRmrfDir => 'Gunakan `rm -rf` untuk menghapus direktori di SFTP'; + @override String get sftpSSHConnected => 'Sftp terhubung'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart index 4f33f6d5..7b147198 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart @@ -171,11 +171,6 @@ class SZh extends S { @override String get download => '下载'; - @override - String downloadStatus(Object percent, Object size) { - return '$size 的 $percent%'; - } - @override String get edit => '编辑'; @@ -437,6 +432,11 @@ class SZh extends S { @override String get path => '路径'; + @override + String percentOfSize(Object percent, Object size) { + return '$size 的 $percent%'; + } + @override String get pickFile => '选择文件'; @@ -558,6 +558,9 @@ class SZh extends S { @override String get sftpDlPrepare => '准备连接至服务器...'; + @override + String get sftpRmrfDir => '在 SFTP 中使用 `rm -rf` 删除文件夹'; + @override String get sftpSSHConnected => 'SFTP 已连接...'; @@ -888,11 +891,6 @@ class SZhTw extends SZh { @override String get download => '下載'; - @override - String downloadStatus(Object percent, Object size) { - return '$size 的 $percent%'; - } - @override String get edit => '編輯'; @@ -1154,6 +1152,11 @@ class SZhTw extends SZh { @override String get path => '路徑'; + @override + String percentOfSize(Object percent, Object size) { + return '$size 的 $percent%'; + } + @override String get pickFile => '選擇文件'; @@ -1275,6 +1278,9 @@ class SZhTw extends SZh { @override String get sftpDlPrepare => '準備連接至服務器...'; + @override + String get sftpRmrfDir => '在 SFTP 中使用 `rm -rf` 刪除文件夾'; + @override String get sftpSSHConnected => 'SFTP 已連接...'; diff --git a/lib/data/model/sftp/req.dart b/lib/data/model/sftp/req.dart index 7b169468..cc8eef6e 100644 --- a/lib/data/model/sftp/req.dart +++ b/lib/data/model/sftp/req.dart @@ -86,4 +86,4 @@ class SftpReqStatus { } } -enum SftpWorkerStatus { preparing, sshConnectted, downloading, finished } +enum SftpWorkerStatus { preparing, sshConnectted, loading, finished } diff --git a/lib/data/model/sftp/worker.dart b/lib/data/model/sftp/worker.dart index c73500a8..2b7fee50 100644 --- a/lib/data/model/sftp/worker.dart +++ b/lib/data/model/sftp/worker.dart @@ -5,9 +5,8 @@ import 'dart:typed_data'; import 'package:dartssh2/dartssh2.dart'; import 'package:easy_isolate/easy_isolate.dart'; -import 'package:toolbox/core/utils/misc.dart'; -import 'package:toolbox/core/utils/server.dart'; +import '../../../core/utils/server.dart'; import 'req.dart'; class SftpWorker { @@ -78,16 +77,14 @@ Future _download( final client = await genClient(req.spi, privateKey: req.privateKey); mainSendPort.send(SftpWorkerStatus.sshConnectted); - final remotePath = req.remotePath; - final localPath = req.localPath; - await Directory(localPath.substring(0, req.localPath.lastIndexOf('/'))) + await Directory(req.localPath.substring(0, req.localPath.lastIndexOf('/'))) .create(recursive: true); - final local = File(localPath); + final local = File(req.localPath); if (await local.exists()) { await local.delete(); } final localFile = local.openWrite(mode: FileMode.append); - final file = await (await client.sftp()).open(remotePath); + final file = await (await client.sftp()).open(req.remotePath); final size = (await file.stat()).size; if (size == null) { mainSendPort.send(Exception('can not get file size')); @@ -97,7 +94,7 @@ Future _download( const defaultChunkSize = 1024 * 1024; final chunkSize = size > defaultChunkSize ? defaultChunkSize : size; mainSendPort.send(size); - mainSendPort.send(SftpWorkerStatus.downloading); + mainSendPort.send(SftpWorkerStatus.loading); for (var i = 0; i < size; i += chunkSize) { final fileData = file.read(length: chunkSize); await for (var form in fileData) { @@ -125,19 +122,23 @@ Future _upload( final client = await genClient(req.spi, privateKey: req.privateKey); mainSendPort.send(SftpWorkerStatus.sshConnectted); - final localPath = req.localPath; - final fileName = getFileName(localPath) ?? 'srvbox_sftp_upload'; - final remotePath = '${req.remotePath}/$fileName'; - final local = File(localPath); + final local = File(req.localPath); if (!await local.exists()) { mainSendPort.send(Exception('local file not exists')); return; } + final localLen = await local.length(); + mainSendPort.send(localLen); + mainSendPort.send(SftpWorkerStatus.loading); final localFile = local.openRead().cast(); final sftp = await client.sftp(); - final file = await sftp.open(remotePath, - mode: SftpFileOpenMode.write | SftpFileOpenMode.create); - final writer = file.write(localFile); + final file = await sftp.open( + req.remotePath, + mode: SftpFileOpenMode.write | SftpFileOpenMode.create, + ); + final writer = file.write(localFile, onProgress: (total) { + mainSendPort.send(total / localLen * 100); + },); await writer.done; await file.close(); mainSendPort.send(watch.elapsed); diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index db70631f..09090524 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -111,4 +111,8 @@ class SettingStore extends PersistentStore { /// Otherwise, display them on the top of server detail page StoreProperty get moveOutServerTabFuncBtns => property('moveOutServerTabFuncBtns', defaultValue: true); + + /// Whether use `rm -rf` to delete directory on SFTP + StoreProperty get sftpRmrfDir => + property('sftpRmrfDir', defaultValue: true); } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 739b1bfd..71678588 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -53,7 +53,6 @@ "dockerStatusRunningAndStoppedFmt": "{runningCount} aktiv, {stoppedCount} container gestoppt.", "dockerStatusRunningFmt": "{count} Container aktiv", "download": "Download", - "downloadStatus": "{percent}% von {size}", "edit": "Bearbeiten", "editVirtKeys": "Virtuelle Tasten bearbeiten", "editor": "Editor", @@ -137,6 +136,7 @@ "open": "Öffnen", "paste": "Einfügen", "path": "Pfad", + "percentOfSize": "{percent}% von {size}", "pickFile": "Datei wählen", "pingAvg": "Avg:", "pingInputIP": "Bitte gib eine Ziel-IP/Domain ein.", @@ -176,6 +176,7 @@ "serverTabUnkown": "Unbekannter Status", "setting": "Einstellungen", "sftpDlPrepare": "Verbindung vorbereiten...", + "sftpRmrfDir": "Verwenden Sie `rm -rf`, um das Verzeichnis auf SFTP zu löschen", "sftpSSHConnected": "SFTP Verbunden", "showDistLogo": "Distributionslogo anzeigen", "snippet": "Snippet", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c157b145..61ae8337 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -53,7 +53,6 @@ "dockerStatusRunningAndStoppedFmt": "{runningCount} running, {stoppedCount} container stopped.", "dockerStatusRunningFmt": "{count} container running.", "download": "Download", - "downloadStatus": "{percent}% of {size}", "edit": "Edit", "editVirtKeys": "Edit virtual keys", "editor": "Editor", @@ -137,6 +136,7 @@ "open": "Open", "paste": "Paste", "path": "Path", + "percentOfSize": "{percent}% of {size}", "pickFile": "Pick file", "pingAvg": "Avg:", "pingInputIP": "Please input a target IP / domain.", @@ -176,6 +176,7 @@ "serverTabUnkown": "Unknown state", "setting": "Settings", "sftpDlPrepare": "Preparing to connect...", + "sftpRmrfDir": "Use `rm -rf` to delete dir on SFTP", "sftpSSHConnected": "SFTP Connected", "showDistLogo": "Show distribution logo", "snippet": "Snippet", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index b2755420..aff7a0e6 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -53,7 +53,6 @@ "dockerStatusRunningAndStoppedFmt": "{runningCount} running, {stoppedCount} container stopped.", "dockerStatusRunningFmt": "{count} wadah berjalan.", "download": "Unduh", - "downloadStatus": "{percent}% dari {size}", "edit": "Edit", "editVirtKeys": "Edit kunci virtual", "editor": "Editor", @@ -137,6 +136,7 @@ "open": "Membuka", "paste": "Tempel", "path": "Jalur", + "percentOfSize": "{percent}% dari {size}", "pickFile": "Pilih file", "pingAvg": "Rata -rata:", "pingInputIP": "Harap masukkan IP / domain target.", @@ -176,6 +176,7 @@ "serverTabUnkown": "Negara yang tidak diketahui", "setting": "Pengaturan", "sftpDlPrepare": "Bersiap untuk terhubung ...", + "sftpRmrfDir": "Gunakan `rm -rf` untuk menghapus direktori di SFTP", "sftpSSHConnected": "Sftp terhubung", "showDistLogo": "Tampilkan logo distribusi", "snippet": "Snippet", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 5d8b4f50..d31c7bde 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -53,7 +53,6 @@ "dockerStatusRunningAndStoppedFmt": "{runningCount}个正在运行, {stoppedCount}个已停止", "dockerStatusRunningFmt": "{count}个容器正在运行", "download": "下载", - "downloadStatus": "{size} 的 {percent}%", "edit": "编辑", "editVirtKeys": "编辑虚拟按键", "editor": "编辑器", @@ -137,6 +136,7 @@ "open": "打开", "paste": "粘贴", "path": "路径", + "percentOfSize": "{size} 的 {percent}%", "pickFile": "选择文件", "pingAvg": "平均:", "pingInputIP": "请输入目标IP或域名", @@ -176,6 +176,7 @@ "serverTabUnkown": "未知状态", "setting": "设置", "sftpDlPrepare": "准备连接至服务器...", + "sftpRmrfDir": "在 SFTP 中使用 `rm -rf` 删除文件夹", "sftpSSHConnected": "SFTP 已连接...", "showDistLogo": "显示发行版 Logo", "snippet": "代码片段", diff --git a/lib/l10n/app_zh_tw.arb b/lib/l10n/app_zh_tw.arb index 80bd354b..56174083 100644 --- a/lib/l10n/app_zh_tw.arb +++ b/lib/l10n/app_zh_tw.arb @@ -53,7 +53,6 @@ "dockerStatusRunningAndStoppedFmt": "{runningCount}個正在運行, {stoppedCount}個已停止", "dockerStatusRunningFmt": "{count}個容器正在運行", "download": "下載", - "downloadStatus": "{size} 的 {percent}%", "edit": "編輯", "editVirtKeys": "編輯虛擬按鍵", "editor": "編輯器", @@ -137,6 +136,7 @@ "open": "打開", "paste": "貼上", "path": "路徑", + "percentOfSize": "{size} 的 {percent}%", "pickFile": "選擇文件", "pingAvg": "平均:", "pingInputIP": "請輸入目標IP或域名", @@ -176,6 +176,7 @@ "serverTabUnkown": "未知狀態", "setting": "設置", "sftpDlPrepare": "準備連接至服務器...", + "sftpRmrfDir": "在 SFTP 中使用 `rm -rf` 刪除文件夾", "sftpSSHConnected": "SFTP 已連接...", "showDistLogo": "顯示發行版 Logo", "snippet": "程式片段", diff --git a/lib/view/page/editor.dart b/lib/view/page/editor.dart index d9e472f3..4e1b0b26 100644 --- a/lib/view/page/editor.dart +++ b/lib/view/page/editor.dart @@ -76,7 +76,18 @@ class _EditorPageState extends State with AfterLayoutMixin { body: _buildBody(), floatingActionButton: FloatingActionButton( child: const Icon(Icons.done), - onPressed: () { + onPressed: () async { + // If path is not null, then it's a file editor + // save the text and return true to pop the page + if (widget.path != null) { + showLoadingDialog(context); + await File(widget.path!).writeAsString(_controller.text); + context.pop(); + context.pop(true); + return; + } + // else it's a text editor + // return the text to the previous page context.pop(_controller.text); }, ), diff --git a/lib/view/page/setting/entry.dart b/lib/view/page/setting/entry.dart index 27442149..1ba6ee6b 100644 --- a/lib/view/page/setting/entry.dart +++ b/lib/view/page/setting/entry.dart @@ -191,6 +191,7 @@ class _SettingPageState extends State { _buildSSHVirtualKeyAutoOff(), _buildKeyboardType(), _buildSSHVirtKeys(), + _buildSftpRmrfDir(), ].map((e) => RoundRectCard(e)).toList(), ); } @@ -1022,4 +1023,11 @@ class _SettingPageState extends State { ], ); } + + Widget _buildSftpRmrfDir() { + return ListTile( + title: Text(_s.sftpRmrfDir), + trailing: buildSwitch(context, _setting.sftpRmrfDir), + ); + } } diff --git a/lib/view/page/storage/local.dart b/lib/view/page/storage/local.dart index eae98601..7af7e1f2 100644 --- a/lib/view/page/storage/local.dart +++ b/lib/view/page/storage/local.dart @@ -315,7 +315,7 @@ class _LocalStoragePageState extends State { } locator().add(SftpReq( spi, - remotePath, + '$remotePath/$fileName', file.absolute.path, SftpReqType.upload, )); diff --git a/lib/view/page/storage/sftp.dart b/lib/view/page/storage/sftp.dart index 1e41c4cb..7ecfbb28 100644 --- a/lib/view/page/storage/sftp.dart +++ b/lib/view/page/storage/sftp.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'dart:typed_data'; +import 'package:after_layout/after_layout.dart'; import 'package:dartssh2/dartssh2.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -25,6 +25,7 @@ import '../../../data/provider/server.dart'; import '../../../data/provider/sftp.dart'; import '../../../data/res/path.dart'; import '../../../data/res/ui.dart'; +import '../../../data/store/setting.dart'; import '../../../locator.dart'; import '../../widget/custom_appbar.dart'; import '../../widget/fade_in.dart'; @@ -47,11 +48,12 @@ class SftpPage extends StatefulWidget { _SftpPageState createState() => _SftpPageState(); } -class _SftpPageState extends State { +class _SftpPageState extends State with AfterLayoutMixin { final SftpBrowserStatus _status = SftpBrowserStatus(); final _sftp = locator(); final _history = locator(); + final _setting = locator(); late S _s; @@ -85,7 +87,6 @@ class _SftpPageState extends State { context.pop(); }, ), - centerTitle: true, title: TwoLineText(up: 'SFTP', down: widget.spi.name), actions: [ IconButton( @@ -191,7 +192,7 @@ class _SftpPageState extends State { _sftp.add( SftpReq( widget.spi, - remotePath, + '$remotePath/${path.split('/').last}', path, SftpReqType.upload, ), @@ -267,9 +268,6 @@ class _SftpPageState extends State { Widget _buildFileView() { if (_status.files == null) { - final p_ = widget.initPath ?? '/'; - _status.path = AbsolutePath(p_); - _listDir(path: p_, client: _client); return centerLoading; } @@ -288,7 +286,7 @@ class _SftpPageState extends State { itemBuilder: (_, index) => _buildItem(_status.files![index]), ), ), - onRefresh: () => _listDir(path: _status.path?.path), + onRefresh: () => _listDir(), ); } @@ -390,8 +388,8 @@ class _SftpPageState extends State { await completer.future; context.pop(); - final result = await AppRoute.editor(path: localPath).go(context); - if (result != null) { + final result = await AppRoute.editor(path: localPath).go(context); + if (result != null && result) { _sftp.add(SftpReq(req.spi, remotePath, localPath, SftpReqType.upload)); } } @@ -431,7 +429,8 @@ class _SftpPageState extends State { void _delete(BuildContext context, SftpName file) { context.pop(); final isDir = file.attr.isDirectory; - final dirText = isDir ? '\n${_s.sureDirEmpty}' : ''; + final useRmrf = _setting.sftpRmrfDir.fetch()!; + final dirText = (isDir && !useRmrf) ? '\n${_s.sureDirEmpty}' : ''; final text = '${_s.sureDelete(file.filename)}$dirText'; final child = Text(text); showRoundDialog( @@ -449,7 +448,9 @@ class _SftpPageState extends State { showLoadingDialog(context); final remotePath = _getRemotePath(file); try { - if (file.attr.isDirectory) { + if (useRmrf) { + await _client!.run('rm -rf "$remotePath"'); + } else if (file.attr.isDirectory) { await _status.client!.rmdir(remotePath); } else { await _status.client!.remove(remotePath); @@ -534,10 +535,6 @@ class _SftpPageState extends State { label: _s.name, ), actions: [ - TextButton( - onPressed: () => context.pop(), - child: Text(_s.cancel), - ), TextButton( onPressed: () async { if (textController.text == '') { @@ -554,9 +551,10 @@ class _SftpPageState extends State { ); return; } + context.pop(); final path = '${_status.path!.path}/${textController.text}'; - final file = await _status.client!.open(path); - await file.writeBytes(Uint8List(0)); + showLoadingDialog(context); + await _client!.run('touch "$path"'); context.pop(); _listDir(); }, @@ -700,6 +698,13 @@ class _SftpPageState extends State { await _listDir(); } } + + @override + FutureOr afterFirstLayout(BuildContext context) { + final p_ = widget.initPath ?? '/'; + _status.path = AbsolutePath(p_); + _listDir(path: p_, client: _client); + } } String? _getDecompressCmd(String filename) { diff --git a/lib/view/page/storage/sftp_mission.dart b/lib/view/page/storage/sftp_mission.dart index 8092e41f..f6d1654c 100644 --- a/lib/view/page/storage/sftp_mission.dart +++ b/lib/view/page/storage/sftp_mission.dart @@ -86,12 +86,12 @@ class _SftpMissionPageState extends State { ], ), ); - case SftpWorkerStatus.downloading: + case SftpWorkerStatus.loading: final percentStr = (status.progress ?? 0.0).toStringAsFixed(2); final size = (status.size ?? 0).convertBytes; return _wrapInCard( status: status, - subtitle: _s.downloadStatus(percentStr, size), + subtitle: _s.percentOfSize(percentStr, size), trailing: _buildDelete(status.fileName, status.id), ); case SftpWorkerStatus.preparing: