new & fix

- new: switch of sftp `rm -rf` dir
- fix: sftp upload
- fix: sftp uploading progress
- opt.: editor will save content if is editing file
This commit is contained in:
lollipopkit
2023-08-27 00:10:01 +08:00
parent 65de4a8ca5
commit 7abadfb5a6
18 changed files with 129 additions and 74 deletions

View File

@@ -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:

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 已連接...';

View File

@@ -86,4 +86,4 @@ class SftpReqStatus {
}
}
enum SftpWorkerStatus { preparing, sshConnectted, downloading, finished }
enum SftpWorkerStatus { preparing, sshConnectted, loading, finished }

View File

@@ -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<void> _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<void> _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<void> _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<Uint8List>();
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);

View File

@@ -111,4 +111,8 @@ class SettingStore extends PersistentStore {
/// Otherwise, display them on the top of server detail page
StoreProperty<bool> get moveOutServerTabFuncBtns =>
property('moveOutServerTabFuncBtns', defaultValue: true);
/// Whether use `rm -rf` to delete directory on SFTP
StoreProperty<bool> get sftpRmrfDir =>
property('sftpRmrfDir', defaultValue: true);
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "代码片段",

View File

@@ -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": "程式片段",

View File

@@ -76,7 +76,18 @@ class _EditorPageState extends State<EditorPage> 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);
},
),

View File

@@ -191,6 +191,7 @@ class _SettingPageState extends State<SettingPage> {
_buildSSHVirtualKeyAutoOff(),
_buildKeyboardType(),
_buildSSHVirtKeys(),
_buildSftpRmrfDir(),
].map((e) => RoundRectCard(e)).toList(),
);
}
@@ -1022,4 +1023,11 @@ class _SettingPageState extends State<SettingPage> {
],
);
}
Widget _buildSftpRmrfDir() {
return ListTile(
title: Text(_s.sftpRmrfDir),
trailing: buildSwitch(context, _setting.sftpRmrfDir),
);
}
}

View File

@@ -315,7 +315,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
}
locator<SftpProvider>().add(SftpReq(
spi,
remotePath,
'$remotePath/$fileName',
file.absolute.path,
SftpReqType.upload,
));

View File

@@ -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<SftpPage> {
class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
final SftpBrowserStatus _status = SftpBrowserStatus();
final _sftp = locator<SftpProvider>();
final _history = locator<HistoryStore>();
final _setting = locator<SettingStore>();
late S _s;
@@ -85,7 +87,6 @@ class _SftpPageState extends State<SftpPage> {
context.pop();
},
),
centerTitle: true,
title: TwoLineText(up: 'SFTP', down: widget.spi.name),
actions: [
IconButton(
@@ -191,7 +192,7 @@ class _SftpPageState extends State<SftpPage> {
_sftp.add(
SftpReq(
widget.spi,
remotePath,
'$remotePath/${path.split('/').last}',
path,
SftpReqType.upload,
),
@@ -267,9 +268,6 @@ class _SftpPageState extends State<SftpPage> {
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<SftpPage> {
itemBuilder: (_, index) => _buildItem(_status.files![index]),
),
),
onRefresh: () => _listDir(path: _status.path?.path),
onRefresh: () => _listDir(),
);
}
@@ -390,8 +388,8 @@ class _SftpPageState extends State<SftpPage> {
await completer.future;
context.pop();
final result = await AppRoute.editor(path: localPath).go<String>(context);
if (result != null) {
final result = await AppRoute.editor(path: localPath).go<bool>(context);
if (result != null && result) {
_sftp.add(SftpReq(req.spi, remotePath, localPath, SftpReqType.upload));
}
}
@@ -431,7 +429,8 @@ class _SftpPageState extends State<SftpPage> {
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<SftpPage> {
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<SftpPage> {
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<SftpPage> {
);
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<SftpPage> {
await _listDir();
}
}
@override
FutureOr<void> afterFirstLayout(BuildContext context) {
final p_ = widget.initPath ?? '/';
_status.path = AbsolutePath(p_);
_listDir(path: p_, client: _client);
}
}
String? _getDecompressCmd(String filename) {

View File

@@ -86,12 +86,12 @@ class _SftpMissionPageState extends State<SftpMissionPage> {
],
),
);
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: