mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
@@ -900,6 +900,12 @@ abstract class S {
|
|||||||
/// **'Save'**
|
/// **'Save'**
|
||||||
String get save;
|
String get save;
|
||||||
|
|
||||||
|
/// No description provided for @saved.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Saved'**
|
||||||
|
String get saved;
|
||||||
|
|
||||||
/// No description provided for @second.
|
/// No description provided for @second.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -432,6 +432,9 @@ class SDe extends S {
|
|||||||
@override
|
@override
|
||||||
String get save => 'Speichern';
|
String get save => 'Speichern';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get saved => 'Gerettet';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get second => 's';
|
String get second => 's';
|
||||||
|
|
||||||
|
|||||||
@@ -432,6 +432,9 @@ class SEn extends S {
|
|||||||
@override
|
@override
|
||||||
String get save => 'Save';
|
String get save => 'Save';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get saved => 'Saved';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get second => 's';
|
String get second => 's';
|
||||||
|
|
||||||
|
|||||||
@@ -432,6 +432,9 @@ class SZh extends S {
|
|||||||
@override
|
@override
|
||||||
String get save => '保存';
|
String get save => '保存';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get saved => '已保存';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get second => '秒';
|
String get second => '秒';
|
||||||
|
|
||||||
@@ -1024,6 +1027,9 @@ class SZhTw extends SZh {
|
|||||||
@override
|
@override
|
||||||
String get save => '保存';
|
String get save => '保存';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get saved => '已保存';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get second => '秒';
|
String get second => '秒';
|
||||||
|
|
||||||
|
|||||||
15
lib/core/extension/sftpfile.dart
Normal file
15
lib/core/extension/sftpfile.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
|
|
||||||
|
extension SftpFile on SftpFileMode {
|
||||||
|
String get str {
|
||||||
|
final user = getRoleMode(userRead, userWrite, userExecute);
|
||||||
|
final group = getRoleMode(groupRead, groupWrite, groupExecute);
|
||||||
|
final other = getRoleMode(otherRead, otherWrite, otherExecute);
|
||||||
|
|
||||||
|
return '$user$group$other';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getRoleMode(bool r, bool w, bool x) {
|
||||||
|
return '${r ? 'r' : '-'}${w ? 'w' : '-'}${x ? 'x' : '-'}';
|
||||||
|
}
|
||||||
@@ -7,8 +7,11 @@ class AppRoute {
|
|||||||
|
|
||||||
AppRoute(this.page, this.title);
|
AppRoute(this.page, this.title);
|
||||||
|
|
||||||
void go(BuildContext context) {
|
Future<T?> go<T>(BuildContext context) {
|
||||||
Analysis.recordView(title);
|
Analysis.recordView(title);
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => page));
|
return Navigator.push<T>(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => page),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,3 +67,9 @@ String? getFileName(String? path) {
|
|||||||
void rebuildAll(BuildContext context) {
|
void rebuildAll(BuildContext context) {
|
||||||
RebuildWidget.restartApp(context);
|
RebuildWidget.restartApp(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getTime(int? unixMill) {
|
||||||
|
return DateTime.fromMillisecondsSinceEpoch((unixMill ?? 0) * 1000)
|
||||||
|
.toString()
|
||||||
|
.replaceFirst('.000', '');
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import '../../../core/extension/stringx.dart';
|
import '../../../core/extension/stringx.dart';
|
||||||
import '../../res/misc.dart';
|
import '../../res/misc.dart';
|
||||||
|
|
||||||
class ConnStatus {
|
class Conn {
|
||||||
final int maxConn;
|
final int maxConn;
|
||||||
final int active;
|
final int active;
|
||||||
final int passive;
|
final int passive;
|
||||||
final int fail;
|
final int fail;
|
||||||
|
|
||||||
ConnStatus({
|
Conn({
|
||||||
required this.maxConn,
|
required this.maxConn,
|
||||||
required this.active,
|
required this.active,
|
||||||
required this.passive,
|
required this.passive,
|
||||||
@@ -15,13 +15,13 @@ class ConnStatus {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnStatus? parseConn(String raw) {
|
Conn? parseConn(String raw) {
|
||||||
final lines = raw.split('\n');
|
final lines = raw.split('\n');
|
||||||
final idx = lines.lastWhere((element) => element.startsWith('Tcp:'),
|
final idx = lines.lastWhere((element) => element.startsWith('Tcp:'),
|
||||||
orElse: () => '');
|
orElse: () => '');
|
||||||
if (idx != '') {
|
if (idx != '') {
|
||||||
final vals = idx.split(numReg);
|
final vals = idx.split(numReg);
|
||||||
return ConnStatus(
|
return Conn(
|
||||||
maxConn: vals[5].i,
|
maxConn: vals[5].i,
|
||||||
active: vals[6].i,
|
active: vals[6].i,
|
||||||
passive: vals[7].i,
|
passive: vals[7].i,
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
class CpuStatus {
|
class Cpus {
|
||||||
List<OneTimeCpuStatus> _pre;
|
List<OneTimeCpuStatus> _pre;
|
||||||
List<OneTimeCpuStatus> _now;
|
List<OneTimeCpuStatus> _now;
|
||||||
CpuStatus(this._pre, this._now);
|
Cpus(this._pre, this._now);
|
||||||
|
|
||||||
double usedPercent({int coreIdx = 0}) {
|
double usedPercent({int coreIdx = 0}) {
|
||||||
if (_now.length != _pre.length) return 0;
|
if (_now.length != _pre.length) return 0;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import '../../res/misc.dart';
|
import '../../res/misc.dart';
|
||||||
|
|
||||||
class DiskInfo {
|
class Disk {
|
||||||
final String path;
|
final String path;
|
||||||
final String loc;
|
final String loc;
|
||||||
final int usedPercent;
|
final int usedPercent;
|
||||||
@@ -8,7 +8,7 @@ class DiskInfo {
|
|||||||
final String size;
|
final String size;
|
||||||
final String avail;
|
final String avail;
|
||||||
|
|
||||||
DiskInfo({
|
Disk({
|
||||||
required this.path,
|
required this.path,
|
||||||
required this.loc,
|
required this.loc,
|
||||||
required this.usedPercent,
|
required this.usedPercent,
|
||||||
@@ -18,8 +18,8 @@ class DiskInfo {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DiskInfo> parseDisk(String raw) {
|
List<Disk> parseDisk(String raw) {
|
||||||
final list = <DiskInfo>[];
|
final list = <Disk>[];
|
||||||
final items = raw.split('\n');
|
final items = raw.split('\n');
|
||||||
items.removeAt(0);
|
items.removeAt(0);
|
||||||
var pathCache = '';
|
var pathCache = '';
|
||||||
@@ -36,7 +36,7 @@ List<DiskInfo> parseDisk(String raw) {
|
|||||||
vals[0] = pathCache;
|
vals[0] = pathCache;
|
||||||
pathCache = '';
|
pathCache = '';
|
||||||
}
|
}
|
||||||
list.add(DiskInfo(
|
list.add(Disk(
|
||||||
path: vals[0],
|
path: vals[0],
|
||||||
loc: vals[5],
|
loc: vals[5],
|
||||||
usedPercent: int.parse(vals[4].replaceFirst('%', '')),
|
usedPercent: int.parse(vals[4].replaceFirst('%', '')),
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import 'package:toolbox/data/model/server/temp.dart';
|
import 'package:toolbox/data/model/server/temp.dart';
|
||||||
|
|
||||||
import 'cpu_status.dart';
|
import 'cpu.dart';
|
||||||
import 'disk_info.dart';
|
import 'disk.dart';
|
||||||
import 'memory.dart';
|
import 'memory.dart';
|
||||||
import 'net_speed.dart';
|
import 'net_speed.dart';
|
||||||
import 'conn_status.dart';
|
import 'conn.dart';
|
||||||
|
|
||||||
class ServerStatus {
|
class ServerStatus {
|
||||||
CpuStatus cpu;
|
Cpus cpu;
|
||||||
Memory mem;
|
Memory mem;
|
||||||
Swap swap;
|
Swap swap;
|
||||||
String sysVer;
|
String sysVer;
|
||||||
String uptime;
|
String uptime;
|
||||||
List<DiskInfo> disk;
|
List<Disk> disk;
|
||||||
ConnStatus tcp;
|
Conn tcp;
|
||||||
NetSpeed netSpeed;
|
NetSpeed netSpeed;
|
||||||
Temperatures temps;
|
Temperatures temps;
|
||||||
String? failedInfo;
|
String? failedInfo;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import '../../res/server_cmd.dart';
|
import '../../res/server_cmd.dart';
|
||||||
import 'cpu_status.dart';
|
import 'cpu.dart';
|
||||||
import 'disk_info.dart';
|
import 'disk.dart';
|
||||||
import 'memory.dart';
|
import 'memory.dart';
|
||||||
import 'net_speed.dart';
|
import 'net_speed.dart';
|
||||||
import 'server_status.dart';
|
import 'server_status.dart';
|
||||||
import 'conn_status.dart';
|
import 'conn.dart';
|
||||||
|
|
||||||
class ServerStatusUpdateReq {
|
class ServerStatusUpdateReq {
|
||||||
final ServerStatus ss;
|
final ServerStatus ss;
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ class SftpDownloadWorker {
|
|||||||
mainSendPort.send((i + form.length) / size * 100);
|
mainSendPort.send((i + form.length) / size * 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
localFile.close();
|
await localFile.close();
|
||||||
|
await file.close();
|
||||||
mainSendPort.send(watch.elapsed);
|
mainSendPort.send(watch.elapsed);
|
||||||
mainSendPort.send(SftpWorkerStatus.finished);
|
mainSendPort.send(SftpWorkerStatus.finished);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ class VirtualKey {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VirtualKeyFunc { toggleIME, backspace, copy, paste, snippet }
|
enum VirtualKeyFunc { toggleIME, backspace, copy, paste, snippet, file }
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import 'package:highlight/languages/nix.dart';
|
|||||||
import 'package:highlight/languages/objectivec.dart';
|
import 'package:highlight/languages/objectivec.dart';
|
||||||
import 'package:highlight/languages/perl.dart';
|
import 'package:highlight/languages/perl.dart';
|
||||||
import 'package:highlight/languages/php.dart';
|
import 'package:highlight/languages/php.dart';
|
||||||
|
import 'package:highlight/languages/plaintext.dart';
|
||||||
import 'package:highlight/languages/powershell.dart';
|
import 'package:highlight/languages/powershell.dart';
|
||||||
import 'package:highlight/languages/python.dart';
|
import 'package:highlight/languages/python.dart';
|
||||||
import 'package:highlight/languages/ruby.dart';
|
import 'package:highlight/languages/ruby.dart';
|
||||||
@@ -34,7 +35,7 @@ import 'package:highlight/languages/yaml.dart';
|
|||||||
|
|
||||||
// KEY: fileNameSuffix
|
// KEY: fileNameSuffix
|
||||||
// VAL: highlight
|
// VAL: highlight
|
||||||
final _suffix2HighlightMap = {
|
final suffix2HighlightMap = {
|
||||||
'dart': dart,
|
'dart': dart,
|
||||||
'go': go,
|
'go': go,
|
||||||
'rust': rust,
|
'rust': rust,
|
||||||
@@ -68,12 +69,16 @@ final _suffix2HighlightMap = {
|
|||||||
'html': htmlbars,
|
'html': htmlbars,
|
||||||
'tex': tex,
|
'tex': tex,
|
||||||
'vim': vim,
|
'vim': vim,
|
||||||
|
'plaintext': plaintext,
|
||||||
};
|
};
|
||||||
|
|
||||||
extension HighlightString on String? {
|
extension HighlightString on String? {
|
||||||
Mode? get highlight {
|
Mode? get highlight {
|
||||||
|
return suffix2HighlightMap[highlightCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get highlightCode {
|
||||||
if (this == null) return null;
|
if (this == null) return null;
|
||||||
final suffix = this!.split('.').last;
|
return this!.split('.').last;
|
||||||
return _suffix2HighlightMap[suffix];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ final numReg = RegExp(r'\s{1,}');
|
|||||||
/// Private Key max allowed size is 20kb
|
/// Private Key max allowed size is 20kb
|
||||||
const privateKeyMaxSize = 20 * 1024;
|
const privateKeyMaxSize = 20 * 1024;
|
||||||
|
|
||||||
|
// Editor max allowed size is 1mb
|
||||||
|
const editorMaxSize = 1024 * 1024;
|
||||||
|
|
||||||
/// Max debug log lines
|
/// Max debug log lines
|
||||||
const maxDebugLogLines = 100;
|
const maxDebugLogLines = 100;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'package:toolbox/data/model/server/temp.dart';
|
import 'package:toolbox/data/model/server/temp.dart';
|
||||||
|
|
||||||
import '../model/server/cpu_status.dart';
|
import '../model/server/cpu.dart';
|
||||||
import '../model/server/disk_info.dart';
|
import '../model/server/disk.dart';
|
||||||
import '../model/server/memory.dart';
|
import '../model/server/memory.dart';
|
||||||
import '../model/server/net_speed.dart';
|
import '../model/server/net_speed.dart';
|
||||||
import '../model/server/server_status.dart';
|
import '../model/server/server_status.dart';
|
||||||
import '../model/server/conn_status.dart';
|
import '../model/server/conn.dart';
|
||||||
|
|
||||||
Memory get _initMemory => Memory(
|
Memory get _initMemory => Memory(
|
||||||
total: 1,
|
total: 1,
|
||||||
@@ -23,7 +23,7 @@ OneTimeCpuStatus get _initOneTimeCpuStatus => OneTimeCpuStatus(
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
CpuStatus get initCpuStatus => CpuStatus(
|
Cpus get initCpuStatus => Cpus(
|
||||||
[_initOneTimeCpuStatus],
|
[_initOneTimeCpuStatus],
|
||||||
[_initOneTimeCpuStatus],
|
[_initOneTimeCpuStatus],
|
||||||
);
|
);
|
||||||
@@ -48,7 +48,7 @@ ServerStatus get initStatus => ServerStatus(
|
|||||||
sysVer: 'Loading...',
|
sysVer: 'Loading...',
|
||||||
uptime: '',
|
uptime: '',
|
||||||
disk: [
|
disk: [
|
||||||
DiskInfo(
|
Disk(
|
||||||
path: '/',
|
path: '/',
|
||||||
loc: '/',
|
loc: '/',
|
||||||
usedPercent: 0,
|
usedPercent: 0,
|
||||||
@@ -57,7 +57,7 @@ ServerStatus get initStatus => ServerStatus(
|
|||||||
avail: '0',
|
avail: '0',
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
tcp: ConnStatus(maxConn: 0, active: 0, passive: 0, fail: 0),
|
tcp: Conn(maxConn: 0, active: 0, passive: 0, fail: 0),
|
||||||
netSpeed: initNetSpeed,
|
netSpeed: initNetSpeed,
|
||||||
swap: _initSwap,
|
swap: _initSwap,
|
||||||
temps: Temperatures(),
|
temps: Temperatures(),
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ final virtualKeys = [
|
|||||||
VirtualKey('Up', key: TerminalKey.arrowUp, icon: Icons.arrow_upward),
|
VirtualKey('Up', key: TerminalKey.arrowUp, icon: Icons.arrow_upward),
|
||||||
VirtualKey('End', key: TerminalKey.end),
|
VirtualKey('End', key: TerminalKey.end),
|
||||||
VirtualKey(
|
VirtualKey(
|
||||||
'Del',
|
'File',
|
||||||
key: TerminalKey.delete,
|
func: VirtualKeyFunc.file,
|
||||||
icon: Icons.backspace,
|
icon: Icons.file_open,
|
||||||
),
|
),
|
||||||
VirtualKey('Snippet', func: VirtualKeyFunc.snippet, icon: Icons.code),
|
VirtualKey('Snippet', func: VirtualKeyFunc.snippet, icon: Icons.code),
|
||||||
VirtualKey('Tab', key: TerminalKey.tab),
|
VirtualKey('Tab', key: TerminalKey.tab),
|
||||||
|
|||||||
@@ -134,6 +134,7 @@
|
|||||||
"result": "Result",
|
"result": "Result",
|
||||||
"run": "Ausführen",
|
"run": "Ausführen",
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
|
"saved": "Gerettet",
|
||||||
"second": "s",
|
"second": "s",
|
||||||
"server": "Server",
|
"server": "Server",
|
||||||
"serverTabConnecting": "Verbinden...",
|
"serverTabConnecting": "Verbinden...",
|
||||||
|
|||||||
@@ -134,6 +134,7 @@
|
|||||||
"result": "Result",
|
"result": "Result",
|
||||||
"run": "Run",
|
"run": "Run",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"saved": "Saved",
|
||||||
"second": "s",
|
"second": "s",
|
||||||
"server": "Server",
|
"server": "Server",
|
||||||
"serverTabConnecting": "Connecting...",
|
"serverTabConnecting": "Connecting...",
|
||||||
|
|||||||
@@ -134,6 +134,7 @@
|
|||||||
"result": "结果",
|
"result": "结果",
|
||||||
"run": "运行",
|
"run": "运行",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
|
"saved": "已保存",
|
||||||
"second": "秒",
|
"second": "秒",
|
||||||
"server": "服务器",
|
"server": "服务器",
|
||||||
"serverTabConnecting": "连接中...",
|
"serverTabConnecting": "连接中...",
|
||||||
|
|||||||
@@ -134,6 +134,7 @@
|
|||||||
"result": "結果",
|
"result": "結果",
|
||||||
"run": "運行",
|
"run": "運行",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
|
"saved": "已保存",
|
||||||
"second": "秒",
|
"second": "秒",
|
||||||
"server": "服務器",
|
"server": "服務器",
|
||||||
"serverTabConnecting": "連接中...",
|
"serverTabConnecting": "連接中...",
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:code_text_field/code_text_field.dart';
|
import 'package:code_text_field/code_text_field.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:flutter_highlight/theme_map.dart';
|
import 'package:flutter_highlight/theme_map.dart';
|
||||||
import 'package:flutter_highlight/themes/monokai.dart';
|
import 'package:flutter_highlight/themes/monokai.dart';
|
||||||
import 'package:toolbox/core/extension/navigator.dart';
|
import 'package:toolbox/core/extension/navigator.dart';
|
||||||
|
import 'package:toolbox/core/utils/misc.dart';
|
||||||
import 'package:toolbox/data/res/highlight.dart';
|
import 'package:toolbox/data/res/highlight.dart';
|
||||||
import 'package:toolbox/data/store/setting.dart';
|
import 'package:toolbox/data/store/setting.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
|
|
||||||
|
import '../widget/two_line_text.dart';
|
||||||
|
|
||||||
class EditorPage extends StatefulWidget {
|
class EditorPage extends StatefulWidget {
|
||||||
|
final String? path;
|
||||||
final String? initCode;
|
final String? initCode;
|
||||||
final String? fileName;
|
const EditorPage({Key? key, this.path, this.initCode}) : super(key: key);
|
||||||
const EditorPage({Key? key, this.initCode, this.fileName}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_EditorPageState createState() => _EditorPageState();
|
_EditorPageState createState() => _EditorPageState();
|
||||||
@@ -21,16 +27,30 @@ class _EditorPageState extends State<EditorPage> {
|
|||||||
late final _focusNode = FocusNode();
|
late final _focusNode = FocusNode();
|
||||||
final _setting = locator<SettingStore>();
|
final _setting = locator<SettingStore>();
|
||||||
late Map<String, TextStyle> _codeTheme;
|
late Map<String, TextStyle> _codeTheme;
|
||||||
|
late S _s;
|
||||||
|
late String? _langCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_focusNode.requestFocus();
|
_langCode = widget.path.highlightCode;
|
||||||
_controller = CodeController(
|
_controller = CodeController(
|
||||||
text: widget.initCode,
|
text: widget.initCode,
|
||||||
language: widget.fileName.highlight,
|
language: suffix2HighlightMap[_langCode ?? 'plaintext'],
|
||||||
);
|
);
|
||||||
_codeTheme = themeMap[_setting.editorTheme.fetch()] ?? monokaiTheme;
|
_codeTheme = themeMap[_setting.editorTheme.fetch()] ?? monokaiTheme;
|
||||||
|
if (widget.initCode == null && widget.path != null) {
|
||||||
|
File(widget.path!)
|
||||||
|
.readAsString()
|
||||||
|
.then((value) => _controller.text = value);
|
||||||
|
}
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_s = S.of(context)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -45,23 +65,41 @@ class _EditorPageState extends State<EditorPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: _codeTheme['root']!.backgroundColor,
|
backgroundColor: _codeTheme['root']!.backgroundColor,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(widget.fileName ?? ''),
|
title: TwoLineText(up: getFileName(widget.path) ?? '', down: _s.editor),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
PopupMenuButton(
|
||||||
icon: const Icon(Icons.done),
|
icon: const Icon(Icons.language),
|
||||||
onPressed: () {
|
onSelected: (value) {
|
||||||
context.pop(_controller.text);
|
_controller.language = suffix2HighlightMap[value];
|
||||||
},
|
},
|
||||||
),
|
initialValue: _langCode,
|
||||||
|
itemBuilder: (BuildContext context) {
|
||||||
|
return suffix2HighlightMap.keys.map((e) {
|
||||||
|
return PopupMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(e),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: CodeTheme(
|
body: SingleChildScrollView(
|
||||||
data: CodeThemeData(styles: _codeTheme),
|
child: CodeTheme(
|
||||||
child: CodeField(
|
data: CodeThemeData(styles: _codeTheme),
|
||||||
controller: _controller,
|
child: CodeField(
|
||||||
textStyle: const TextStyle(fontFamily: 'SourceCode'),
|
focusNode: _focusNode,
|
||||||
|
controller: _controller,
|
||||||
|
textStyle: const TextStyle(fontFamily: 'SourceCode'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
child: const Icon(Icons.done),
|
||||||
|
onPressed: () {
|
||||||
|
context.pop(_controller.text);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:toolbox/core/extension/order.dart';
|
import 'package:toolbox/core/extension/order.dart';
|
||||||
import 'package:toolbox/data/model/server/cpu_status.dart';
|
import 'package:toolbox/data/model/server/cpu.dart';
|
||||||
import 'package:toolbox/data/model/server/disk_info.dart';
|
import 'package:toolbox/data/model/server/disk.dart';
|
||||||
import 'package:toolbox/data/model/server/dist.dart';
|
import 'package:toolbox/data/model/server/dist.dart';
|
||||||
import 'package:toolbox/data/model/server/memory.dart';
|
import 'package:toolbox/data/model/server/memory.dart';
|
||||||
import 'package:toolbox/data/model/server/temp.dart';
|
import 'package:toolbox/data/model/server/temp.dart';
|
||||||
@@ -118,7 +118,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCPUView(CpuStatus cs) {
|
Widget _buildCPUView(Cpus cs) {
|
||||||
return RoundRectCard(
|
return RoundRectCard(
|
||||||
Padding(
|
Padding(
|
||||||
padding: roundRectCardPadding,
|
padding: roundRectCardPadding,
|
||||||
@@ -171,7 +171,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCPUProgress(CpuStatus cs) {
|
Widget _buildCPUProgress(Cpus cs) {
|
||||||
final children = <Widget>[];
|
final children = <Widget>[];
|
||||||
for (var i = 0; i < cs.coresCount; i++) {
|
for (var i = 0; i < cs.coresCount; i++) {
|
||||||
if (i == 0) continue;
|
if (i == 0) continue;
|
||||||
@@ -288,7 +288,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDiskView(List<DiskInfo> disk) {
|
Widget _buildDiskView(List<Disk> disk) {
|
||||||
disk.removeWhere((e) {
|
disk.removeWhere((e) {
|
||||||
for (final ingorePath in _setting.diskIgnorePath.fetch()!) {
|
for (final ingorePath in _setting.diskIgnorePath.fetch()!) {
|
||||||
if (e.path.startsWith(ingorePath)) return true;
|
if (e.path.startsWith(ingorePath)) return true;
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
_buildTitle('SSH'),
|
_buildTitle('SSH'),
|
||||||
_buildSSH(),
|
_buildSSH(),
|
||||||
// Editor
|
// Editor
|
||||||
_buildTitle('Editor'),
|
_buildTitle(_s.editor),
|
||||||
_buildEditor(),
|
_buildEditor(),
|
||||||
const SizedBox(height: 37),
|
const SizedBox(height: 37),
|
||||||
],
|
],
|
||||||
@@ -535,17 +535,18 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pop();
|
context.pop();
|
||||||
final fontSize = double.tryParse(ctrller.text);
|
final fontSize = double.tryParse(ctrller.text);
|
||||||
if (fontSize == null) {
|
if (fontSize == null) {
|
||||||
showRoundDialog(context: context, child: Text(_s.failed));
|
showRoundDialog(context: context, child: Text(_s.failed));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_fontSize = fontSize;
|
_fontSize = fontSize;
|
||||||
_setting.termFontSize.put(_fontSize);
|
_setting.termFontSize.put(_fontSize);
|
||||||
},
|
},
|
||||||
child: Text(_s.ok)),
|
child: Text(_s.ok),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -559,23 +560,25 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
trailing: Text(_s.edit, style: textSize15),
|
trailing: Text(_s.edit, style: textSize15),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showRoundDialog(
|
showRoundDialog(
|
||||||
context: context,
|
context: context,
|
||||||
child: Input(
|
title: Text(_s.diskIgnorePath),
|
||||||
controller: TextEditingController(text: json.encode(paths)),
|
child: Input(
|
||||||
label: 'JSON',
|
controller: TextEditingController(text: json.encode(paths)),
|
||||||
type: TextInputType.visiblePassword,
|
label: 'JSON',
|
||||||
maxLines: 3,
|
type: TextInputType.visiblePassword,
|
||||||
onSubmitted: (p0) {
|
maxLines: 3,
|
||||||
try {
|
onSubmitted: (p0) {
|
||||||
final list = List<String>.from(json.decode(p0));
|
try {
|
||||||
_setting.diskIgnorePath.put(list);
|
final list = List<String>.from(json.decode(p0));
|
||||||
context.pop();
|
_setting.diskIgnorePath.put(list);
|
||||||
showSnackBar(context, Text(_s.success));
|
context.pop();
|
||||||
} catch (e) {
|
showSnackBar(context, Text(_s.success));
|
||||||
showSnackBar(context, Text(e.toString()));
|
} catch (e) {
|
||||||
}
|
showSnackBar(context, Text(e.toString()));
|
||||||
},
|
}
|
||||||
));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -641,7 +644,6 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
_editorTheme = idx;
|
_editorTheme = idx;
|
||||||
});
|
});
|
||||||
_setting.editorTheme.put(idx);
|
_setting.editorTheme.put(idx);
|
||||||
_showRestartSnackbar();
|
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
_editorTheme,
|
_editorTheme,
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import 'dart:io';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:toolbox/core/extension/navigator.dart';
|
import 'package:toolbox/core/extension/navigator.dart';
|
||||||
|
import 'package:toolbox/data/res/misc.dart';
|
||||||
import 'package:toolbox/view/page/editor.dart';
|
import 'package:toolbox/view/page/editor.dart';
|
||||||
|
import 'package:toolbox/view/widget/input_field.dart';
|
||||||
|
|
||||||
import '../../../core/extension/numx.dart';
|
import '../../../core/extension/numx.dart';
|
||||||
import '../../../core/extension/stringx.dart';
|
import '../../../core/extension/stringx.dart';
|
||||||
@@ -141,6 +143,55 @@ class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.edit),
|
||||||
|
title: Text(_s.edit),
|
||||||
|
onTap: () async {
|
||||||
|
context.pop();
|
||||||
|
final stat = await file.stat();
|
||||||
|
if (stat.size > editorMaxSize) {
|
||||||
|
showRoundDialog(
|
||||||
|
context: context,
|
||||||
|
child: Text(_s.fileTooLarge(fileName, stat.size, '1m')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final f = File(file.absolute.path);
|
||||||
|
final data = await f.readAsString();
|
||||||
|
final result = await AppRoute(
|
||||||
|
EditorPage(
|
||||||
|
initCode: data,
|
||||||
|
path: fileName,
|
||||||
|
),
|
||||||
|
'sftp dled editor',
|
||||||
|
).go<String>(context);
|
||||||
|
if (result != null) {
|
||||||
|
f.writeAsString(result);
|
||||||
|
showSnackBar(context, Text(_s.saved));
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.abc),
|
||||||
|
title: Text(_s.rename),
|
||||||
|
onTap: () {
|
||||||
|
context.pop();
|
||||||
|
showRoundDialog(
|
||||||
|
context: context,
|
||||||
|
title: Text(_s.rename),
|
||||||
|
child: Input(
|
||||||
|
controller: TextEditingController(text: fileName),
|
||||||
|
onSubmitted: (p0) {
|
||||||
|
context.pop();
|
||||||
|
final newPath = '${file.parent.path}/$p0';
|
||||||
|
file.renameSync(newPath);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.delete),
|
leading: const Icon(Icons.delete),
|
||||||
title: Text(_s.delete),
|
title: Text(_s.delete),
|
||||||
@@ -173,31 +224,8 @@ class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
|
|||||||
shareFiles(context, [file.absolute.path]);
|
shareFiles(context, [file.absolute.path]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.edit),
|
|
||||||
title: Text(_s.edit),
|
|
||||||
onTap: () async {
|
|
||||||
context.pop();
|
|
||||||
final stat = await file.stat();
|
|
||||||
if (stat.size > 1024 * 1024) {
|
|
||||||
showRoundDialog(
|
|
||||||
context: context,
|
|
||||||
child: Text(_s.fileTooLarge(fileName, stat.size, '1m')),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final f = await File(file.absolute.path).readAsString();
|
|
||||||
AppRoute(EditorPage(initCode: f), 'sftp dled editor').go(context);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: (() => context.pop()),
|
|
||||||
child: Text(_s.close),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:toolbox/core/extension/navigator.dart';
|
import 'package:toolbox/core/extension/navigator.dart';
|
||||||
|
import 'package:toolbox/core/extension/sftpfile.dart';
|
||||||
|
import 'package:toolbox/data/res/misc.dart';
|
||||||
|
import 'package:toolbox/view/page/editor.dart';
|
||||||
|
|
||||||
import '../../../core/extension/numx.dart';
|
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/ui.dart';
|
import '../../../core/utils/ui.dart';
|
||||||
import '../../../data/model/server/server.dart';
|
import '../../../data/model/server/server.dart';
|
||||||
import '../../../data/model/server/server_private_info.dart';
|
import '../../../data/model/server/server_private_info.dart';
|
||||||
@@ -39,7 +44,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
|
|
||||||
late S _s;
|
late S _s;
|
||||||
|
|
||||||
Server? _si;
|
ServerState? _state;
|
||||||
SSHClient? _client;
|
SSHClient? _client;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -52,8 +57,8 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final serverProvider = locator<ServerProvider>();
|
final serverProvider = locator<ServerProvider>();
|
||||||
_si = serverProvider.servers[widget.spi.id];
|
_client = serverProvider.servers[widget.spi.id]?.client;
|
||||||
_client = _si?.client;
|
_state = serverProvider.servers[widget.spi.id]?.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -92,7 +97,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await backward();
|
await _backward();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
),
|
),
|
||||||
@@ -116,11 +121,11 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.folder),
|
leading: const Icon(Icons.folder),
|
||||||
title: Text(_s.createFolder),
|
title: Text(_s.createFolder),
|
||||||
onTap: () => mkdir(context)),
|
onTap: () => _mkdir(context)),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.insert_drive_file),
|
leading: const Icon(Icons.insert_drive_file),
|
||||||
title: Text(_s.createFile),
|
title: Text(_s.createFile),
|
||||||
onTap: () => newFile(context)),
|
onTap: () => _newFile(context)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -163,14 +168,14 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_status.path?.update(p!);
|
_status.path?.update(p!);
|
||||||
listDir(path: p);
|
_listDir(path: p);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.gps_fixed),
|
icon: const Icon(Icons.gps_fixed),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFileView() {
|
Widget _buildFileView() {
|
||||||
if (_client == null || _si?.state != ServerState.connected) {
|
if (_client == null || _state != ServerState.connected) {
|
||||||
return centerLoading;
|
return centerLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +185,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
|
|
||||||
if (_status.files == null) {
|
if (_status.files == null) {
|
||||||
_status.path = AbsolutePath('/');
|
_status.path = AbsolutePath('/');
|
||||||
listDir(path: '/', client: _client);
|
_listDir(path: '/', client: _client);
|
||||||
return centerLoading;
|
return centerLoading;
|
||||||
} else {
|
} else {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
@@ -196,7 +201,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file),
|
leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file),
|
||||||
title: Text(file.filename),
|
title: Text(file.filename),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
'${getTime(file.attr.modifyTime)}\n${getMode(file.attr.mode)}',
|
'${getTime(file.attr.modifyTime)}\n${file.attr.mode?.str ?? ''}',
|
||||||
style: const TextStyle(color: Colors.grey),
|
style: const TextStyle(color: Colors.grey),
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
),
|
),
|
||||||
@@ -205,80 +210,105 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
if (isDir) {
|
if (isDir) {
|
||||||
_status.path?.update(file.filename);
|
_status.path?.update(file.filename);
|
||||||
listDir(path: _status.path?.path);
|
_listDir(path: _status.path?.path);
|
||||||
} else {
|
} else {
|
||||||
onItemPress(context, file, true);
|
_onItemPress(context, file, true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPress: () => onItemPress(context, file, false),
|
onLongPress: () => _onItemPress(context, file, false),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onRefresh: () => listDir(path: _status.path?.path),
|
onRefresh: () => _listDir(path: _status.path?.path),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTime(int? unixMill) {
|
void _onItemPress(BuildContext context, SftpName file, bool notDir) {
|
||||||
return DateTime.fromMillisecondsSinceEpoch((unixMill ?? 0) * 1000)
|
|
||||||
.toString()
|
|
||||||
.replaceFirst('.000', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
String getMode(SftpFileMode? mode) {
|
|
||||||
if (mode == null) {
|
|
||||||
return '---';
|
|
||||||
}
|
|
||||||
|
|
||||||
final user = getRoleMode(mode.userRead, mode.userWrite, mode.userExecute);
|
|
||||||
final group =
|
|
||||||
getRoleMode(mode.groupRead, mode.groupWrite, mode.groupExecute);
|
|
||||||
final other =
|
|
||||||
getRoleMode(mode.otherRead, mode.otherWrite, mode.otherExecute);
|
|
||||||
|
|
||||||
return '$user$group$other';
|
|
||||||
}
|
|
||||||
|
|
||||||
String getRoleMode(bool r, bool w, bool x) {
|
|
||||||
return '${r ? 'r' : '-'}${w ? 'w' : '-'}${x ? 'x' : '-'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
void onItemPress(BuildContext context, SftpName file, bool showDownload) {
|
|
||||||
showRoundDialog(
|
showRoundDialog(
|
||||||
context: context,
|
context: context,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
notDir
|
||||||
|
? ListTile(
|
||||||
|
leading: const Icon(Icons.edit),
|
||||||
|
title: Text(_s.edit),
|
||||||
|
onTap: () => _edit(context, file),
|
||||||
|
)
|
||||||
|
: placeholder,
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.delete),
|
leading: const Icon(Icons.delete),
|
||||||
title: Text(_s.delete),
|
title: Text(_s.delete),
|
||||||
onTap: () => delete(context, file),
|
onTap: () => _delete(context, file),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.edit),
|
leading: const Icon(Icons.abc),
|
||||||
title: Text(_s.rename),
|
title: Text(_s.rename),
|
||||||
onTap: () => rename(context, file),
|
onTap: () => _rename(context, file),
|
||||||
),
|
),
|
||||||
showDownload
|
notDir
|
||||||
? ListTile(
|
? ListTile(
|
||||||
leading: const Icon(Icons.download),
|
leading: const Icon(Icons.download),
|
||||||
title: Text(_s.download),
|
title: Text(_s.download),
|
||||||
onTap: () => download(context, file),
|
onTap: () => _download(context, file),
|
||||||
)
|
)
|
||||||
: placeholder
|
: placeholder,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => context.pop(),
|
|
||||||
child: Text(_s.cancel),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void download(BuildContext context, SftpName name) {
|
Future<void> _edit(BuildContext context, SftpName name) async {
|
||||||
|
final size = name.attr.size;
|
||||||
|
if (size == null || size > editorMaxSize) {
|
||||||
|
showSnackBar(
|
||||||
|
context,
|
||||||
|
Text(_s.fileTooLarge(
|
||||||
|
name.filename,
|
||||||
|
size ?? 0,
|
||||||
|
editorMaxSize,
|
||||||
|
)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final file = await _status.client!.open(
|
||||||
|
_getRemotePath(name),
|
||||||
|
mode: SftpFileOpenMode.read | SftpFileOpenMode.write,
|
||||||
|
);
|
||||||
|
final localPath = '${(await sftpDir).path}${_getRemotePath(name)}';
|
||||||
|
await Directory(localPath.substring(0, localPath.lastIndexOf('/')))
|
||||||
|
.create(recursive: true);
|
||||||
|
final local = File(localPath);
|
||||||
|
if (await local.exists()) {
|
||||||
|
await local.delete();
|
||||||
|
}
|
||||||
|
final localFile = local.openWrite(mode: FileMode.append);
|
||||||
|
const defaultChunkSize = 1024 * 1024;
|
||||||
|
final chunkSize = size > defaultChunkSize ? defaultChunkSize : size;
|
||||||
|
for (var i = 0; i < size; i += chunkSize) {
|
||||||
|
final fileData = file.read(length: chunkSize);
|
||||||
|
await for (var form in fileData) {
|
||||||
|
localFile.add(form);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await localFile.close();
|
||||||
|
context.pop();
|
||||||
|
|
||||||
|
final result = await AppRoute(
|
||||||
|
EditorPage(path: localPath),
|
||||||
|
'SFTP edit',
|
||||||
|
).go<String>(context);
|
||||||
|
if (result != null) {
|
||||||
|
await local.writeAsString(result);
|
||||||
|
await file.writeBytes(result.uint8List);
|
||||||
|
showSnackBar(context, Text(_s.saved));
|
||||||
|
}
|
||||||
|
await file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _download(BuildContext context, SftpName name) {
|
||||||
showRoundDialog(
|
showRoundDialog(
|
||||||
context: context,
|
context: context,
|
||||||
child: Text('${_s.dl2Local(name.filename)}\n${_s.keepForeground}'),
|
child: Text('${_s.dl2Local(name.filename)}\n${_s.keepForeground}'),
|
||||||
@@ -313,7 +343,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void delete(BuildContext context, SftpName file) {
|
void _delete(BuildContext context, SftpName file) {
|
||||||
context.pop();
|
context.pop();
|
||||||
final isDir = file.attr.isDirectory;
|
final isDir = file.attr.isDirectory;
|
||||||
final dirText = isDir ? '\n${_s.sureDirEmpty}' : '';
|
final dirText = isDir ? '\n${_s.sureDirEmpty}' : '';
|
||||||
@@ -358,7 +388,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listDir();
|
_listDir();
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
_s.delete,
|
_s.delete,
|
||||||
@@ -369,7 +399,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mkdir(BuildContext context) {
|
void _mkdir(BuildContext context) {
|
||||||
context.pop();
|
context.pop();
|
||||||
final textController = TextEditingController();
|
final textController = TextEditingController();
|
||||||
showRoundDialog(
|
showRoundDialog(
|
||||||
@@ -402,7 +432,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
_status.client!
|
_status.client!
|
||||||
.mkdir('${_status.path!.path}/${textController.text}');
|
.mkdir('${_status.path!.path}/${textController.text}');
|
||||||
context.pop();
|
context.pop();
|
||||||
listDir();
|
_listDir();
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
_s.ok,
|
_s.ok,
|
||||||
@@ -413,7 +443,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void newFile(BuildContext context) {
|
void _newFile(BuildContext context) {
|
||||||
context.pop();
|
context.pop();
|
||||||
final textController = TextEditingController();
|
final textController = TextEditingController();
|
||||||
showRoundDialog(
|
showRoundDialog(
|
||||||
@@ -447,7 +477,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
.open('${_status.path!.path}/${textController.text}'))
|
.open('${_status.path!.path}/${textController.text}'))
|
||||||
.writeBytes(Uint8List(0));
|
.writeBytes(Uint8List(0));
|
||||||
context.pop();
|
context.pop();
|
||||||
listDir();
|
_listDir();
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
_s.ok,
|
_s.ok,
|
||||||
@@ -458,7 +488,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void rename(BuildContext context, SftpName file) {
|
void _rename(BuildContext context, SftpName file) {
|
||||||
context.pop();
|
context.pop();
|
||||||
final textController = TextEditingController();
|
final textController = TextEditingController();
|
||||||
showRoundDialog(
|
showRoundDialog(
|
||||||
@@ -487,7 +517,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
}
|
}
|
||||||
await _status.client!.rename(file.filename, textController.text);
|
await _status.client!.rename(file.filename, textController.text);
|
||||||
context.pop();
|
context.pop();
|
||||||
listDir();
|
_listDir();
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
_s.rename,
|
_s.rename,
|
||||||
@@ -503,7 +533,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
return prePath + (prePath.endsWith('/') ? '' : '/') + name.filename;
|
return prePath + (prePath.endsWith('/') ? '' : '/') + name.filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> listDir({String? path, SSHClient? client}) async {
|
Future<void> _listDir({String? path, SSHClient? client}) async {
|
||||||
if (_status.isBusy) {
|
if (_status.isBusy) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -535,13 +565,13 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
await backward();
|
await _backward();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> backward() async {
|
Future<void> _backward() async {
|
||||||
if (_status.path!.undo()) {
|
if (_status.path!.undo()) {
|
||||||
await listDir();
|
await _listDir();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,6 +228,9 @@ class _SSHPageState extends State<SSHPage> {
|
|||||||
_terminal.keyInput(TerminalKey.enter);
|
_terminal.keyInput(TerminalKey.enter);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case VirtualKeyFunc.file:
|
||||||
|
// TODO
|
||||||
|
showRoundDialog(context: context, child: const Text('TODO'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user