Merge pull request #897 from Kingtous/flutter_desktop
add: file transfer flutter desktop adaptation
This commit is contained in:
commit
400d1455bd
@ -73,8 +73,9 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
backgroundColor: MyTheme.grayBg,
|
backgroundColor: MyTheme.grayBg,
|
||||||
body: Row(
|
body: Row(
|
||||||
children: [
|
children: [
|
||||||
Flexible(flex: 1, child: body(isLocal: true)),
|
Flexible(flex: 3, child: body(isLocal: true)),
|
||||||
Flexible(flex: 1, child: body(isLocal: false))
|
Flexible(flex: 3, child: body(isLocal: false)),
|
||||||
|
Flexible(flex: 2, child: statusList())
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomSheet: bottomSheet(),
|
bottomSheet: bottomSheet(),
|
||||||
@ -117,8 +118,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.folder_outlined,
|
Icon(Icons.folder_outlined, color: Colors.black),
|
||||||
color: Colors.black),
|
|
||||||
SizedBox(width: 5),
|
SizedBox(width: 5),
|
||||||
Text(translate("Create Folder"))
|
Text(translate("Create Folder"))
|
||||||
],
|
],
|
||||||
@ -149,16 +149,15 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
model.toggleSelectMode();
|
model.toggleSelectMode();
|
||||||
} else if (v == "folder") {
|
} else if (v == "folder") {
|
||||||
final name = TextEditingController();
|
final name = TextEditingController();
|
||||||
DialogManager.show((setState, close) =>
|
DialogManager.show((setState, close) => CustomAlertDialog(
|
||||||
CustomAlertDialog(
|
|
||||||
title: Text(translate("Create Folder")),
|
title: Text(translate("Create Folder")),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: translate(
|
labelText:
|
||||||
"Please enter the folder name"),
|
translate("Please enter the folder name"),
|
||||||
),
|
),
|
||||||
controller: name,
|
controller: name,
|
||||||
),
|
),
|
||||||
@ -191,110 +190,195 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
Widget body({bool isLocal = false}) {
|
Widget body({bool isLocal = false}) {
|
||||||
final fd = isLocal ? model.currentLocalDir : model.currentRemoteDir;
|
final fd = isLocal ? model.currentLocalDir : model.currentRemoteDir;
|
||||||
final entries = fd.entries;
|
final entries = fd.entries;
|
||||||
return Column(children: [
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white70, border: Border.all(color: Colors.grey)),
|
||||||
|
margin: const EdgeInsets.all(16.0),
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
headTools(isLocal),
|
headTools(isLocal),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: Row(
|
||||||
itemCount: entries.length + 1,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
itemBuilder: (context, index) {
|
children: [
|
||||||
if (index >= entries.length) {
|
Expanded(
|
||||||
return listTail();
|
child: SingleChildScrollView(
|
||||||
}
|
child: DataTable(
|
||||||
var selected = false;
|
showCheckboxColumn: true,
|
||||||
if (model.selectMode) {
|
dataRowHeight: 30,
|
||||||
selected = _selectedItems.contains(entries[index]);
|
columnSpacing: 8,
|
||||||
}
|
columns: [
|
||||||
|
DataColumn(label: Text(translate(" "))), // icon
|
||||||
final sizeStr = entries[index].isFile
|
DataColumn(
|
||||||
? readableFileSize(entries[index].size.toDouble())
|
label: Text(
|
||||||
|
translate("Name"),
|
||||||
|
)),
|
||||||
|
DataColumn(label: Text(translate("Modified"))),
|
||||||
|
DataColumn(label: Text(translate("Size"))),
|
||||||
|
],
|
||||||
|
rows: entries.map((entry) {
|
||||||
|
final sizeStr = entry.isFile
|
||||||
|
? readableFileSize(entry.size.toDouble())
|
||||||
: "";
|
: "";
|
||||||
return Card(
|
return DataRow(
|
||||||
child: ListTile(
|
key: ValueKey(entry.name),
|
||||||
leading: Icon(
|
onSelectChanged: (s) {
|
||||||
entries[index].isFile ? Icons.feed_outlined : Icons.folder,
|
// TODO
|
||||||
size: 40),
|
|
||||||
title: Text(entries[index].name),
|
|
||||||
selected: selected,
|
|
||||||
subtitle: Text(
|
|
||||||
entries[index]
|
|
||||||
.lastModified()
|
|
||||||
.toString()
|
|
||||||
.replaceAll(".000", "") +
|
|
||||||
" " +
|
|
||||||
sizeStr,
|
|
||||||
style: TextStyle(fontSize: 12, color: MyTheme.darkGray),
|
|
||||||
),
|
|
||||||
trailing: needShowCheckBox()
|
|
||||||
? Checkbox(
|
|
||||||
value: selected,
|
|
||||||
onChanged: (v) {
|
|
||||||
if (v == null) return;
|
|
||||||
if (v && !selected) {
|
|
||||||
_selectedItems.add(isLocal, entries[index]);
|
|
||||||
} else if (!v && selected) {
|
|
||||||
_selectedItems.remove(entries[index]);
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
})
|
|
||||||
: PopupMenuButton<String>(
|
|
||||||
icon: Icon(Icons.more_vert),
|
|
||||||
itemBuilder: (context) {
|
|
||||||
return [
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Text(translate("Delete")),
|
|
||||||
value: "delete",
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Text(translate("Multi Select")),
|
|
||||||
value: "multi_select",
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Text(translate("Properties")),
|
|
||||||
value: "properties",
|
|
||||||
enabled: false,
|
|
||||||
)
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
onSelected: (v) {
|
cells: [
|
||||||
if (v == "delete") {
|
// TODO: icon
|
||||||
final items = SelectedItems();
|
DataCell(Icon(
|
||||||
items.add(isLocal, entries[index]);
|
entry.isFile ? Icons.feed_outlined : Icons.folder,
|
||||||
model.removeAction(items);
|
size: 25)),
|
||||||
} else if (v == "multi_select") {
|
DataCell(
|
||||||
_selectedItems.clear();
|
ConstrainedBox(
|
||||||
model.toggleSelectMode();
|
constraints: BoxConstraints(maxWidth: 100),
|
||||||
}
|
child: Text(entry.name,
|
||||||
}),
|
overflow: TextOverflow.ellipsis)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (model.selectMode && !_selectedItems.isOtherPage(isLocal)) {
|
if (entry.isDirectory) {
|
||||||
if (selected) {
|
model.openDirectory(entry.path, isLocal: isLocal);
|
||||||
_selectedItems.remove(entries[index]);
|
|
||||||
} else {
|
|
||||||
_selectedItems.add(isLocal, entries[index]);
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (entries[index].isDirectory) {
|
|
||||||
model.openDirectory(entries[index].path, isLocal: isLocal);
|
|
||||||
breadCrumbScrollToEnd(isLocal);
|
|
||||||
} else {
|
} else {
|
||||||
// Perform file-related tasks.
|
// Perform file-related tasks.
|
||||||
}
|
}
|
||||||
},
|
}),
|
||||||
onLongPress: () {
|
DataCell(Text(
|
||||||
_selectedItems.clear();
|
entry
|
||||||
model.toggleSelectMode();
|
.lastModified()
|
||||||
if (model.selectMode) {
|
.toString()
|
||||||
_selectedItems.add(isLocal, entries[index]);
|
.replaceAll(".000", "") +
|
||||||
}
|
" ",
|
||||||
setState(() {});
|
style: TextStyle(
|
||||||
},
|
fontSize: 12, color: MyTheme.darkGray),
|
||||||
),
|
)),
|
||||||
);
|
DataCell(Text(
|
||||||
},
|
sizeStr,
|
||||||
))
|
style: TextStyle(
|
||||||
|
fontSize: 12, color: MyTheme.darkGray),
|
||||||
|
)),
|
||||||
]);
|
]);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
Center(child: listTail(isLocal: isLocal)),
|
||||||
|
// Expanded(
|
||||||
|
// child: ListView.builder(
|
||||||
|
// itemCount: entries.length + 1,
|
||||||
|
// itemBuilder: (context, index) {
|
||||||
|
// if (index >= entries.length) {
|
||||||
|
// return listTail(isLocal: isLocal);
|
||||||
|
// }
|
||||||
|
// var selected = false;
|
||||||
|
// if (model.selectMode) {
|
||||||
|
// selected = _selectedItems.contains(entries[index]);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// final sizeStr = entries[index].isFile
|
||||||
|
// ? readableFileSize(entries[index].size.toDouble())
|
||||||
|
// : "";
|
||||||
|
// return Card(
|
||||||
|
// child: ListTile(
|
||||||
|
// leading: Icon(
|
||||||
|
// entries[index].isFile ? Icons.feed_outlined : Icons.folder,
|
||||||
|
// size: 40),
|
||||||
|
// title: Text(entries[index].name),
|
||||||
|
// selected: selected,
|
||||||
|
// subtitle: Text(
|
||||||
|
// entries[index]
|
||||||
|
// .lastModified()
|
||||||
|
// .toString()
|
||||||
|
// .replaceAll(".000", "") +
|
||||||
|
// " " +
|
||||||
|
// sizeStr,
|
||||||
|
// style: TextStyle(fontSize: 12, color: MyTheme.darkGray),
|
||||||
|
// ),
|
||||||
|
// trailing: needShowCheckBox()
|
||||||
|
// ? Checkbox(
|
||||||
|
// value: selected,
|
||||||
|
// onChanged: (v) {
|
||||||
|
// if (v == null) return;
|
||||||
|
// if (v && !selected) {
|
||||||
|
// _selectedItems.add(isLocal, entries[index]);
|
||||||
|
// } else if (!v && selected) {
|
||||||
|
// _selectedItems.remove(entries[index]);
|
||||||
|
// }
|
||||||
|
// setState(() {});
|
||||||
|
// })
|
||||||
|
// : PopupMenuButton<String>(
|
||||||
|
// icon: Icon(Icons.more_vert),
|
||||||
|
// itemBuilder: (context) {
|
||||||
|
// return [
|
||||||
|
// PopupMenuItem(
|
||||||
|
// child: Text(translate("Delete")),
|
||||||
|
// value: "delete",
|
||||||
|
// ),
|
||||||
|
// PopupMenuItem(
|
||||||
|
// child: Text(translate("Multi Select")),
|
||||||
|
// value: "multi_select",
|
||||||
|
// ),
|
||||||
|
// PopupMenuItem(
|
||||||
|
// child: Text(translate("Properties")),
|
||||||
|
// value: "properties",
|
||||||
|
// enabled: false,
|
||||||
|
// )
|
||||||
|
// ];
|
||||||
|
// },
|
||||||
|
// onSelected: (v) {
|
||||||
|
// if (v == "delete") {
|
||||||
|
// final items = SelectedItems();
|
||||||
|
// items.add(isLocal, entries[index]);
|
||||||
|
// model.removeAction(items);
|
||||||
|
// } else if (v == "multi_select") {
|
||||||
|
// _selectedItems.clear();
|
||||||
|
// model.toggleSelectMode();
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// onTap: () {
|
||||||
|
// if (model.selectMode && !_selectedItems.isOtherPage(isLocal)) {
|
||||||
|
// if (selected) {
|
||||||
|
// _selectedItems.remove(entries[index]);
|
||||||
|
// } else {
|
||||||
|
// _selectedItems.add(isLocal, entries[index]);
|
||||||
|
// }
|
||||||
|
// setState(() {});
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (entries[index].isDirectory) {
|
||||||
|
// model.openDirectory(entries[index].path, isLocal: isLocal);
|
||||||
|
// breadCrumbScrollToEnd(isLocal);
|
||||||
|
// } else {
|
||||||
|
// // Perform file-related tasks.
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// onLongPress: () {
|
||||||
|
// _selectedItems.clear();
|
||||||
|
// model.toggleSelectMode();
|
||||||
|
// if (model.selectMode) {
|
||||||
|
// _selectedItems.add(isLocal, entries[index]);
|
||||||
|
// }
|
||||||
|
// setState(() {});
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ))
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// transfer status list
|
||||||
|
/// watch transfer status
|
||||||
|
Widget statusList() {
|
||||||
|
return PreferredSize(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(top: 16.0,bottom: 16.0, right: 16.0),
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
decoration: BoxDecoration(color: Colors.white70,border: Border.all(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
preferredSize: Size(200, double.infinity));
|
||||||
}
|
}
|
||||||
|
|
||||||
goBack({bool? isLocal}) {
|
goBack({bool? isLocal}) {
|
||||||
@ -302,10 +386,10 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
breadCrumbScrollToEnd(bool isLocal) {
|
breadCrumbScrollToEnd(bool isLocal) {
|
||||||
final controller = isLocal ? _breadCrumbLocalScroller : _breadCrumbRemoteScroller;
|
final controller =
|
||||||
|
isLocal ? _breadCrumbLocalScroller : _breadCrumbRemoteScroller;
|
||||||
Future.delayed(Duration(milliseconds: 200), () {
|
Future.delayed(Duration(milliseconds: 200), () {
|
||||||
controller.animateTo(
|
controller.animateTo(controller.position.maxScrollExtent,
|
||||||
controller.position.maxScrollExtent,
|
|
||||||
duration: Duration(milliseconds: 200),
|
duration: Duration(milliseconds: 200),
|
||||||
curve: Curves.fastLinearToSlowEaseIn);
|
curve: Curves.fastLinearToSlowEaseIn);
|
||||||
});
|
});
|
||||||
@ -332,7 +416,10 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
model.openDirectory(path, isLocal: isLocal);
|
model.openDirectory(path, isLocal: isLocal);
|
||||||
}, isLocal),
|
}, isLocal),
|
||||||
divider: Icon(Icons.chevron_right),
|
divider: Icon(Icons.chevron_right),
|
||||||
overflow: ScrollableOverflow(controller: isLocal ? _breadCrumbLocalScroller : _breadCrumbRemoteScroller),
|
overflow: ScrollableOverflow(
|
||||||
|
controller: isLocal
|
||||||
|
? _breadCrumbLocalScroller
|
||||||
|
: _breadCrumbRemoteScroller),
|
||||||
)),
|
)),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -362,7 +449,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
],
|
],
|
||||||
));
|
));
|
||||||
|
|
||||||
Widget listTail() {
|
Widget listTail({bool isLocal = false}) {
|
||||||
|
final dir = isLocal ? model.currentLocalDir : model.currentRemoteDir;
|
||||||
return Container(
|
return Container(
|
||||||
height: 100,
|
height: 100,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -370,14 +458,14 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(30, 5, 30, 0),
|
padding: EdgeInsets.fromLTRB(30, 5, 30, 0),
|
||||||
child: Text(
|
child: Text(
|
||||||
model.currentDir.path,
|
dir.path,
|
||||||
style: TextStyle(color: MyTheme.darkGray),
|
style: TextStyle(color: MyTheme.darkGray),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.all(2),
|
padding: EdgeInsets.all(2),
|
||||||
child: Text(
|
child: Text(
|
||||||
"${translate("Total")}: ${model.currentDir.entries.length} ${translate("items")}",
|
"${translate("Total")}: ${dir.entries.length} ${translate("items")}",
|
||||||
style: TextStyle(color: MyTheme.darkGray),
|
style: TextStyle(color: MyTheme.darkGray),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -466,8 +554,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BreadCrumbItem> getPathBreadCrumbItems(
|
List<BreadCrumbItem> getPathBreadCrumbItems(void Function() onHome,
|
||||||
void Function() onHome, void Function(List<String>) onPressed, bool isLocal) {
|
void Function(List<String>) onPressed, bool isLocal) {
|
||||||
final path = model.shortPath(isLocal);
|
final path = model.shortPath(isLocal);
|
||||||
final list = PathUtil.split(path, model.currentIsWindows);
|
final list = PathUtil.split(path, model.currentIsWindows);
|
||||||
final breadCrumbList = [
|
final breadCrumbList = [
|
||||||
|
@ -170,19 +170,18 @@ class FileModel extends ChangeNotifier {
|
|||||||
if (false == resp) {
|
if (false == resp) {
|
||||||
cancelJob(int.tryParse(evt['id']) ?? 0);
|
cancelJob(int.tryParse(evt['id']) ?? 0);
|
||||||
} else {
|
} else {
|
||||||
var msg = Map()
|
var need_override = false;
|
||||||
..['id'] = evt['id']
|
|
||||||
..['file_num'] = evt['file_num']
|
|
||||||
..['is_upload'] = evt['is_upload']
|
|
||||||
..['remember'] = fileConfirmCheckboxRemember.toString();
|
|
||||||
if (resp == null) {
|
if (resp == null) {
|
||||||
// skip
|
// skip
|
||||||
msg['need_override'] = 'false';
|
need_override = false;
|
||||||
} else {
|
} else {
|
||||||
// overwrite
|
// overwrite
|
||||||
msg['need_override'] = 'true';
|
need_override = true;
|
||||||
}
|
}
|
||||||
_ffi.target?.setByName("set_confirm_override_file", jsonEncode(msg));
|
_ffi.target?.bind.sessionSetConfirmOverrideFile(id: _ffi.target?.id ?? "",
|
||||||
|
actId: evt['id'], fileNum: evt['file_num'],
|
||||||
|
needOverride: need_override, remember: fileConfirmCheckboxRemember,
|
||||||
|
isUpload: evt['is_upload']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,22 +192,21 @@ class FileModel extends ChangeNotifier {
|
|||||||
|
|
||||||
onReady() async {
|
onReady() async {
|
||||||
_localOption.home = _ffi.target?.getByName("get_home_dir") ?? "";
|
_localOption.home = _ffi.target?.getByName("get_home_dir") ?? "";
|
||||||
_localOption.showHidden =
|
_localOption.showHidden = (await _ffi.target?.bind.sessionGetPeerOption
|
||||||
_ffi.target?.getByName("peer_option", "local_show_hidden").isNotEmpty ??
|
(id: _ffi.target?.id ?? "", name: "local_show_hidden"))?.isNotEmpty ?? false;
|
||||||
false;
|
|
||||||
|
|
||||||
_remoteOption.showHidden = _ffi.target
|
_remoteOption.showHidden = (await _ffi.target?.bind.sessionGetPeerOption
|
||||||
?.getByName("peer_option", "remote_show_hidden")
|
(id: _ffi.target?.id ?? "", name: "remote_show_hidden"))?.isNotEmpty ?? false;
|
||||||
.isNotEmpty ??
|
|
||||||
false;
|
|
||||||
_remoteOption.isWindows = _ffi.target?.ffiModel.pi.platform == "Windows";
|
_remoteOption.isWindows = _ffi.target?.ffiModel.pi.platform == "Windows";
|
||||||
|
|
||||||
debugPrint("remote platform: ${_ffi.target?.ffiModel.pi.platform}");
|
debugPrint("remote platform: ${_ffi.target?.ffiModel.pi.platform}");
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 100));
|
await Future.delayed(Duration(milliseconds: 100));
|
||||||
|
|
||||||
final local = _ffi.target?.getByName("peer_option", "local_dir") ?? "";
|
final local = (await _ffi.target?.bind.sessionGetPeerOption
|
||||||
final remote = _ffi.target?.getByName("peer_option", "remote_dir") ?? "";
|
(id: _ffi.target?.id ?? "", name: "local_dir")) ?? "";
|
||||||
|
final remote = (await _ffi.target?.bind.sessionGetPeerOption
|
||||||
|
(id: _ffi.target?.id ?? "", name: "remote_dir")) ?? "";
|
||||||
openDirectory(local.isEmpty ? _localOption.home : local, isLocal: true);
|
openDirectory(local.isEmpty ? _localOption.home : local, isLocal: true);
|
||||||
openDirectory(remote.isEmpty ? _remoteOption.home : remote, isLocal: false);
|
openDirectory(remote.isEmpty ? _remoteOption.home : remote, isLocal: false);
|
||||||
await Future.delayed(Duration(seconds: 1));
|
await Future.delayed(Duration(seconds: 1));
|
||||||
@ -224,23 +222,16 @@ class FileModel extends ChangeNotifier {
|
|||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
|
|
||||||
// save config
|
// save config
|
||||||
Map<String, String> msg = Map();
|
Map<String, String> msgMap = Map();
|
||||||
|
|
||||||
msg["name"] = "local_dir";
|
msgMap["local_dir"] = _currentLocalDir.path;
|
||||||
msg["value"] = _currentLocalDir.path;
|
msgMap["local_show_hidden"] = _localOption.showHidden ? "Y" : "";
|
||||||
_ffi.target?.setByName('peer_option', jsonEncode(msg));
|
msgMap["remote_dir"] = _currentRemoteDir.path;
|
||||||
|
msgMap["remote_show_hidden"] = _remoteOption.showHidden ? "Y" : "";
|
||||||
msg["name"] = "local_show_hidden";
|
final id = _ffi.target?.id ?? "";
|
||||||
msg["value"] = _localOption.showHidden ? "Y" : "";
|
for(final msg in msgMap.entries) {
|
||||||
_ffi.target?.setByName('peer_option', jsonEncode(msg));
|
_ffi.target?.bind.sessionPeerOption(id: id, name: msg.key, value: msg.value);
|
||||||
|
}
|
||||||
msg["name"] = "remote_dir";
|
|
||||||
msg["value"] = _currentRemoteDir.path;
|
|
||||||
_ffi.target?.setByName('peer_option', jsonEncode(msg));
|
|
||||||
|
|
||||||
msg["name"] = "remote_show_hidden";
|
|
||||||
msg["value"] = _remoteOption.showHidden ? "Y" : "";
|
|
||||||
_ffi.target?.setByName('peer_option', jsonEncode(msg));
|
|
||||||
_currentLocalDir.clear();
|
_currentLocalDir.clear();
|
||||||
_currentRemoteDir.clear();
|
_currentRemoteDir.clear();
|
||||||
_localOption.clear();
|
_localOption.clear();
|
||||||
@ -285,7 +276,23 @@ class FileModel extends ChangeNotifier {
|
|||||||
openDirectory(parent, isLocal: isLocal);
|
openDirectory(parent, isLocal: isLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFiles(SelectedItems items) {
|
/// isRemote only for desktop now, [isRemote == true] means [remote -> local]
|
||||||
|
sendFiles(SelectedItems items, {bool isRemote = false}) {
|
||||||
|
if (isDesktop) {
|
||||||
|
// desktop sendFiles
|
||||||
|
_jobProgress.state = JobState.inProgress;
|
||||||
|
final toPath =
|
||||||
|
isRemote ? currentRemoteDir.path : currentLocalDir.path;
|
||||||
|
final isWindows =
|
||||||
|
isRemote ? _localOption.isWindows : _remoteOption.isWindows;
|
||||||
|
final showHidden =
|
||||||
|
isRemote ? _localOption.showHidden : _remoteOption.showHidden ;
|
||||||
|
items.items.forEach((from) async {
|
||||||
|
_jobId++;
|
||||||
|
await _ffi.target?.bind.sessionSendFiles(id: '${_ffi.target?.id}', actId: _jobId, path: from.path, to: PathUtil.join(toPath, from.name, isWindows)
|
||||||
|
,fileNum: 0, includeHidden: showHidden, isRemote: isRemote);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
if (items.isLocal == null) {
|
if (items.isLocal == null) {
|
||||||
debugPrint("Failed to sendFiles ,wrong path state");
|
debugPrint("Failed to sendFiles ,wrong path state");
|
||||||
return;
|
return;
|
||||||
@ -303,6 +310,7 @@ class FileModel extends ChangeNotifier {
|
|||||||
,fileNum: 0, includeHidden: showHidden, isRemote: !(items.isLocal!));
|
,fileNum: 0, includeHidden: showHidden, isRemote: !(items.isLocal!));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool removeCheckboxRemember = false;
|
bool removeCheckboxRemember = false;
|
||||||
|
|
||||||
@ -583,7 +591,7 @@ class FileFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if id == null, means to fetch global FFI
|
// if id == null, means to fetch global FFI
|
||||||
FFI get _ffi => ffi(_id == null ? "" : 'ft_${_id}');
|
FFI get _ffi => ffi(_id ?? "");
|
||||||
|
|
||||||
Future<FileDirectory> registerReadTask(bool isLocal, String path) {
|
Future<FileDirectory> registerReadTask(bool isLocal, String path) {
|
||||||
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
|
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
|
||||||
@ -663,14 +671,7 @@ class FileFetcher {
|
|||||||
int id, String path, bool isLocal, bool showHidden) async {
|
int id, String path, bool isLocal, bool showHidden) async {
|
||||||
// TODO test Recursive is show hidden default?
|
// TODO test Recursive is show hidden default?
|
||||||
try {
|
try {
|
||||||
final msg = {
|
await _ffi.bind.sessionReadDirRecursive(id: _ffi.id, actId: id, path: path, isRemote: !isLocal, showHidden: showHidden);
|
||||||
"id": id.toString(),
|
|
||||||
"path": path,
|
|
||||||
"show_hidden": showHidden.toString(),
|
|
||||||
"is_remote": (!isLocal).toString()
|
|
||||||
};
|
|
||||||
// TODO
|
|
||||||
_ffi.setByName("read_dir_recursive", jsonEncode(msg));
|
|
||||||
return registerReadRecursiveTask(id);
|
return registerReadRecursiveTask(id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Future.error(e);
|
return Future.error(e);
|
||||||
|
@ -15,6 +15,7 @@ use magnum_opus::{Channels::*, Decoder as AudioDecoder};
|
|||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub use file_trait::FileManager;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
allow_err,
|
||||||
anyhow::{anyhow, Context},
|
anyhow::{anyhow, Context},
|
||||||
@ -30,13 +31,14 @@ use hbb_common::{
|
|||||||
tokio::time::Duration,
|
tokio::time::Duration,
|
||||||
AddrMangle, ResultType, Stream,
|
AddrMangle, ResultType, Stream,
|
||||||
};
|
};
|
||||||
|
pub use helper::LatencyController;
|
||||||
use scrap::{Decoder, Image, VideoCodecId};
|
use scrap::{Decoder, Image, VideoCodecId};
|
||||||
|
|
||||||
pub use super::lang::*;
|
pub use super::lang::*;
|
||||||
|
|
||||||
pub mod file_trait;
|
pub mod file_trait;
|
||||||
pub use file_trait::FileManager;
|
|
||||||
pub mod helper;
|
pub mod helper;
|
||||||
pub use helper::LatencyController;
|
|
||||||
pub const SEC30: Duration = Duration::from_secs(30);
|
pub const SEC30: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
/// Client of the remote desktop.
|
/// Client of the remote desktop.
|
||||||
@ -1535,7 +1537,7 @@ pub enum Data {
|
|||||||
Login((String, bool)),
|
Login((String, bool)),
|
||||||
Message(Message),
|
Message(Message),
|
||||||
SendFiles((i32, String, String, i32, bool, bool)),
|
SendFiles((i32, String, String, i32, bool, bool)),
|
||||||
RemoveDirAll((i32, String, bool)),
|
RemoveDirAll((i32, String, bool, bool)),
|
||||||
ConfirmDeleteFiles((i32, i32)),
|
ConfirmDeleteFiles((i32, i32)),
|
||||||
SetNoConfirm(i32),
|
SetNoConfirm(i32),
|
||||||
RemoveDir((i32, String)),
|
RemoveDir((i32, String)),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use super::{Data, Interface};
|
|
||||||
use hbb_common::{fs, message_proto::*};
|
use hbb_common::{fs, message_proto::*};
|
||||||
|
|
||||||
|
use super::{Data, Interface};
|
||||||
|
|
||||||
pub trait FileManager: Interface {
|
pub trait FileManager: Interface {
|
||||||
fn get_home_dir(&self) -> String {
|
fn get_home_dir(&self) -> String {
|
||||||
fs::get_home_as_string()
|
fs::get_home_as_string()
|
||||||
@ -48,8 +49,8 @@ pub trait FileManager: Interface {
|
|||||||
self.send(Data::RemoveFile((id, path, file_num, is_remote)));
|
self.send(Data::RemoveFile((id, path, file_num, is_remote)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_dir_all(&self, id: i32, path: String, is_remote: bool) {
|
fn remove_dir_all(&self, id: i32, path: String, is_remote: bool, include_hidden: bool) {
|
||||||
self.send(Data::RemoveDirAll((id, path, is_remote)));
|
self.send(Data::RemoveDirAll((id, path, is_remote, include_hidden)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_delete_files(&self, id: i32, file_num: i32) {
|
fn confirm_delete_files(&self, id: i32, file_num: i32) {
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
use crate::common::make_fd_to_json;
|
use std::{
|
||||||
use crate::{client::*, flutter_ffi::EventToUI};
|
collections::{HashMap, VecDeque},
|
||||||
|
sync::{Arc, Mutex, RwLock},
|
||||||
|
};
|
||||||
|
|
||||||
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
|
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
|
||||||
|
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
allow_err,
|
||||||
compress::decompress,
|
compress::decompress,
|
||||||
@ -21,10 +25,9 @@ use hbb_common::{
|
|||||||
},
|
},
|
||||||
Stream,
|
Stream,
|
||||||
};
|
};
|
||||||
use std::{
|
|
||||||
collections::{HashMap, VecDeque},
|
use crate::common::make_fd_to_json;
|
||||||
sync::{Arc, Mutex, RwLock},
|
use crate::{client::*, flutter_ffi::EventToUI};
|
||||||
};
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
// static ref SESSION: Arc<RwLock<Option<Session>>> = Default::default();
|
// static ref SESSION: Arc<RwLock<Option<Session>>> = Default::default();
|
||||||
@ -52,9 +55,9 @@ impl Session {
|
|||||||
/// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+
|
/// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+
|
||||||
/// * `is_file_transfer` - If the session is used for file transfer.
|
/// * `is_file_transfer` - If the session is used for file transfer.
|
||||||
pub fn start(identifier: &str, is_file_transfer: bool, events2ui: StreamSink<EventToUI>) {
|
pub fn start(identifier: &str, is_file_transfer: bool, events2ui: StreamSink<EventToUI>) {
|
||||||
LocalConfig::set_remote_id(&identifier);
|
|
||||||
// TODO check same id
|
// TODO check same id
|
||||||
let session_id = get_session_id(identifier.to_owned());
|
let session_id = get_session_id(identifier.to_owned());
|
||||||
|
LocalConfig::set_remote_id(&session_id);
|
||||||
// TODO close
|
// TODO close
|
||||||
// Self::close();
|
// Self::close();
|
||||||
let events2ui = Arc::new(RwLock::new(events2ui));
|
let events2ui = Arc::new(RwLock::new(events2ui));
|
||||||
@ -502,7 +505,11 @@ impl Interface for Session {
|
|||||||
|
|
||||||
if lc.is_file_transfer {
|
if lc.is_file_transfer {
|
||||||
if pi.username.is_empty() {
|
if pi.username.is_empty() {
|
||||||
self.msgbox("error", "Error", "No active console user logged on, please connect and logon first.");
|
self.msgbox(
|
||||||
|
"error",
|
||||||
|
"Error",
|
||||||
|
"No active console user logged on, please connect and logon first.",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -992,20 +999,20 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Data::RemoveDirAll((id, path, is_remote)) => {
|
Data::RemoveDirAll((id, path, is_remote, include_hidden)) => {
|
||||||
if is_remote {
|
if is_remote {
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
let mut file_action = FileAction::new();
|
let mut file_action = FileAction::new();
|
||||||
file_action.set_all_files(ReadAllFiles {
|
file_action.set_all_files(ReadAllFiles {
|
||||||
id,
|
id,
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
include_hidden: true,
|
include_hidden,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
msg_out.set_file_action(file_action);
|
msg_out.set_file_action(file_action);
|
||||||
allow_err!(peer.send(&msg_out).await);
|
allow_err!(peer.send(&msg_out).await);
|
||||||
} else {
|
} else {
|
||||||
match fs::get_recursive_files(&path, true) {
|
match fs::get_recursive_files(&path, include_hidden) {
|
||||||
Ok(entries) => {
|
Ok(entries) => {
|
||||||
let mut fd = FileDirectory::new();
|
let mut fd = FileDirectory::new();
|
||||||
fd.id = id;
|
fd.id = id;
|
||||||
@ -1235,9 +1242,8 @@ pub mod connection_manager {
|
|||||||
sync::{Mutex, RwLock},
|
sync::{Mutex, RwLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::ipc;
|
use serde_derive::Serialize;
|
||||||
use crate::ipc::Data;
|
|
||||||
use crate::server::Connection as Conn;
|
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
allow_err,
|
||||||
config::Config,
|
config::Config,
|
||||||
@ -1254,7 +1260,10 @@ pub mod connection_manager {
|
|||||||
};
|
};
|
||||||
#[cfg(any(target_os = "android"))]
|
#[cfg(any(target_os = "android"))]
|
||||||
use scrap::android::call_main_service_set_by_name;
|
use scrap::android::call_main_service_set_by_name;
|
||||||
use serde_derive::Serialize;
|
|
||||||
|
use crate::ipc;
|
||||||
|
use crate::ipc::Data;
|
||||||
|
use crate::server::Connection as Conn;
|
||||||
|
|
||||||
use super::GLOBAL_EVENT_STREAM;
|
use super::GLOBAL_EVENT_STREAM;
|
||||||
|
|
||||||
|
@ -1,21 +1,24 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
ffi::{CStr, CString},
|
||||||
|
os::raw::c_char,
|
||||||
|
};
|
||||||
|
|
||||||
|
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
|
||||||
|
use serde_json::{Number, Value};
|
||||||
|
|
||||||
|
use hbb_common::ResultType;
|
||||||
|
use hbb_common::{
|
||||||
|
config::{self, Config, LocalConfig, PeerConfig, ONLINE},
|
||||||
|
fs, log,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::client::file_trait::FileManager;
|
use crate::client::file_trait::FileManager;
|
||||||
use crate::common::make_fd_to_json;
|
use crate::common::make_fd_to_json;
|
||||||
use crate::flutter::connection_manager::{self, get_clients_length, get_clients_state};
|
use crate::flutter::connection_manager::{self, get_clients_length, get_clients_state};
|
||||||
use crate::flutter::{self, Session, SESSIONS};
|
use crate::flutter::{self, Session, SESSIONS};
|
||||||
use crate::start_server;
|
use crate::start_server;
|
||||||
use crate::ui_interface;
|
use crate::ui_interface;
|
||||||
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
|
|
||||||
use hbb_common::ResultType;
|
|
||||||
use hbb_common::{
|
|
||||||
config::{self, Config, LocalConfig, PeerConfig, ONLINE},
|
|
||||||
fs, log,
|
|
||||||
};
|
|
||||||
use serde_json::{Number, Value};
|
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
ffi::{CStr, CString},
|
|
||||||
os::raw::c_char,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn initialize(app_dir: &str) {
|
fn initialize(app_dir: &str) {
|
||||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||||
@ -244,6 +247,13 @@ pub fn session_peer_option(id: String, name: String, value: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_get_peer_option(id: String, name: String) -> String {
|
||||||
|
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||||
|
return session.get_option(&name);
|
||||||
|
}
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_input_os_password(id: String, value: String) {
|
pub fn session_input_os_password(id: String, value: String) {
|
||||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||||
session.input_os_password(value, true);
|
session.input_os_password(value, true);
|
||||||
@ -290,9 +300,15 @@ pub fn session_remove_file(id: String, act_id: i32, path: String, file_num: i32,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_read_dir_recursive(id: String, act_id: i32, path: String, is_remote: bool) {
|
pub fn session_read_dir_recursive(
|
||||||
|
id: String,
|
||||||
|
act_id: i32,
|
||||||
|
path: String,
|
||||||
|
is_remote: bool,
|
||||||
|
show_hidden: bool,
|
||||||
|
) {
|
||||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||||
session.remove_dir_all(act_id, path, is_remote);
|
session.remove_dir_all(act_id, path, is_remote, show_hidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -814,13 +830,14 @@ unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) {
|
|||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
pub mod server_side {
|
pub mod server_side {
|
||||||
use hbb_common::{config::Config, log};
|
|
||||||
use jni::{
|
use jni::{
|
||||||
objects::{JClass, JString},
|
objects::{JClass, JString},
|
||||||
sys::jstring,
|
sys::jstring,
|
||||||
JNIEnv,
|
JNIEnv,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use hbb_common::{config::Config, log};
|
||||||
|
|
||||||
use crate::start_server;
|
use crate::start_server;
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -188,7 +188,8 @@ class JobTable: Reactor.Component {
|
|||||||
job.confirmed = true;
|
job.confirmed = true;
|
||||||
return;
|
return;
|
||||||
}else if (job.type == "del-dir"){
|
}else if (job.type == "del-dir"){
|
||||||
handler.remove_dir_all(job.id, job.path, job.is_remote);
|
// TODO: include_hidden is always true
|
||||||
|
handler.remove_dir_all(job.id, job.path, job.is_remote, true);
|
||||||
job.confirmed = true;
|
job.confirmed = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ impl sciter::EventHandler for Handler {
|
|||||||
fn read_remote_dir(String, bool);
|
fn read_remote_dir(String, bool);
|
||||||
fn send_chat(String);
|
fn send_chat(String);
|
||||||
fn switch_display(i32);
|
fn switch_display(i32);
|
||||||
fn remove_dir_all(i32, String, bool);
|
fn remove_dir_all(i32, String, bool, bool);
|
||||||
fn confirm_delete_files(i32, i32);
|
fn confirm_delete_files(i32, i32);
|
||||||
fn set_no_confirm(i32);
|
fn set_no_confirm(i32);
|
||||||
fn cancel_job(i32);
|
fn cancel_job(i32);
|
||||||
@ -1793,7 +1793,7 @@ impl Remote {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Data::RemoveDirAll((id, path, is_remote)) => {
|
Data::RemoveDirAll((id, path, is_remote, include_hidden)) => {
|
||||||
let sep = self.handler.get_path_sep(is_remote);
|
let sep = self.handler.get_path_sep(is_remote);
|
||||||
if is_remote {
|
if is_remote {
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
@ -1801,7 +1801,7 @@ impl Remote {
|
|||||||
file_action.set_all_files(ReadAllFiles {
|
file_action.set_all_files(ReadAllFiles {
|
||||||
id,
|
id,
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
include_hidden: true,
|
include_hidden,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
msg_out.set_file_action(file_action);
|
msg_out.set_file_action(file_action);
|
||||||
@ -1809,7 +1809,7 @@ impl Remote {
|
|||||||
self.remove_jobs
|
self.remove_jobs
|
||||||
.insert(id, RemoveJob::new(Vec::new(), path, sep, is_remote));
|
.insert(id, RemoveJob::new(Vec::new(), path, sep, is_remote));
|
||||||
} else {
|
} else {
|
||||||
match fs::get_recursive_files(&path, true) {
|
match fs::get_recursive_files(&path, include_hidden) {
|
||||||
Ok(entries) => {
|
Ok(entries) => {
|
||||||
let m = make_fd(id, &entries, true);
|
let m = make_fd(id, &entries, true);
|
||||||
self.handler.call("updateFolderFiles", &make_args!(m));
|
self.handler.call("updateFolderFiles", &make_args!(m));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user