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, | ||||
|                 body: Row( | ||||
|                   children: [ | ||||
|                     Flexible(flex: 1, child: body(isLocal: true)), | ||||
|                     Flexible(flex: 1, child: body(isLocal: false)) | ||||
|                     Flexible(flex: 3, child: body(isLocal: true)), | ||||
|                     Flexible(flex: 3, child: body(isLocal: false)), | ||||
|                     Flexible(flex: 2, child: statusList()) | ||||
|                   ], | ||||
|                 ), | ||||
|                 bottomSheet: bottomSheet(), | ||||
| @ -117,8 +118,7 @@ class _FileManagerPageState extends State<FileManagerPage> | ||||
|             PopupMenuItem( | ||||
|               child: Row( | ||||
|                 children: [ | ||||
|                   Icon(Icons.folder_outlined, | ||||
|                       color: Colors.black), | ||||
|                   Icon(Icons.folder_outlined, color: Colors.black), | ||||
|                   SizedBox(width: 5), | ||||
|                   Text(translate("Create Folder")) | ||||
|                 ], | ||||
| @ -149,16 +149,15 @@ class _FileManagerPageState extends State<FileManagerPage> | ||||
|             model.toggleSelectMode(); | ||||
|           } else if (v == "folder") { | ||||
|             final name = TextEditingController(); | ||||
|             DialogManager.show((setState, close) => | ||||
|                 CustomAlertDialog( | ||||
|             DialogManager.show((setState, close) => CustomAlertDialog( | ||||
|                     title: Text(translate("Create Folder")), | ||||
|                     content: Column( | ||||
|                       mainAxisSize: MainAxisSize.min, | ||||
|                       children: [ | ||||
|                         TextFormField( | ||||
|                           decoration: InputDecoration( | ||||
|                             labelText: translate( | ||||
|                                 "Please enter the folder name"), | ||||
|                             labelText: | ||||
|                                 translate("Please enter the folder name"), | ||||
|                           ), | ||||
|                           controller: name, | ||||
|                         ), | ||||
| @ -191,110 +190,195 @@ class _FileManagerPageState extends State<FileManagerPage> | ||||
|   Widget body({bool isLocal = false}) { | ||||
|     final fd = isLocal ? model.currentLocalDir : model.currentRemoteDir; | ||||
|     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), | ||||
|         Expanded( | ||||
|           child: ListView.builder( | ||||
|         itemCount: entries.length + 1, | ||||
|         itemBuilder: (context, index) { | ||||
|           if (index >= entries.length) { | ||||
|             return listTail(); | ||||
|           } | ||||
|           var selected = false; | ||||
|           if (model.selectMode) { | ||||
|             selected = _selectedItems.contains(entries[index]); | ||||
|           } | ||||
| 
 | ||||
|           final sizeStr = entries[index].isFile | ||||
|               ? readableFileSize(entries[index].size.toDouble()) | ||||
|             child: Row( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             Expanded( | ||||
|               child: SingleChildScrollView( | ||||
|                 child: DataTable( | ||||
|                   showCheckboxColumn: true, | ||||
|                   dataRowHeight: 30, | ||||
|                   columnSpacing: 8, | ||||
|                   columns: [ | ||||
|                     DataColumn(label: Text(translate(" "))), // icon | ||||
|                     DataColumn( | ||||
|                         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( | ||||
|             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, | ||||
|                           ) | ||||
|                         ]; | ||||
|                     return DataRow( | ||||
|                         key: ValueKey(entry.name), | ||||
|                         onSelectChanged: (s) { | ||||
|                           // TODO | ||||
|                         }, | ||||
|                       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(); | ||||
|                         } | ||||
|                       }), | ||||
|                         cells: [ | ||||
|                           // TODO: icon | ||||
|                           DataCell(Icon( | ||||
|                               entry.isFile ? Icons.feed_outlined : Icons.folder, | ||||
|                               size: 25)), | ||||
|                           DataCell( | ||||
|                               ConstrainedBox( | ||||
|                                   constraints: BoxConstraints(maxWidth: 100), | ||||
|                                   child: Text(entry.name, | ||||
|                                       overflow: TextOverflow.ellipsis)), | ||||
|                               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); | ||||
|                             if (entry.isDirectory) { | ||||
|                               model.openDirectory(entry.path, isLocal: isLocal); | ||||
|                             } else { | ||||
|                               // Perform file-related tasks. | ||||
|                             } | ||||
|               }, | ||||
|               onLongPress: () { | ||||
|                 _selectedItems.clear(); | ||||
|                 model.toggleSelectMode(); | ||||
|                 if (model.selectMode) { | ||||
|                   _selectedItems.add(isLocal, entries[index]); | ||||
|                 } | ||||
|                 setState(() {}); | ||||
|               }, | ||||
|             ), | ||||
|           ); | ||||
|         }, | ||||
|       )) | ||||
|                           }), | ||||
|                           DataCell(Text( | ||||
|                             entry | ||||
|                                     .lastModified() | ||||
|                                     .toString() | ||||
|                                     .replaceAll(".000", "") + | ||||
|                                 "   ", | ||||
|                             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}) { | ||||
| @ -302,10 +386,10 @@ class _FileManagerPageState extends State<FileManagerPage> | ||||
|   } | ||||
| 
 | ||||
|   breadCrumbScrollToEnd(bool isLocal) { | ||||
|     final controller = isLocal ? _breadCrumbLocalScroller : _breadCrumbRemoteScroller; | ||||
|     final controller = | ||||
|         isLocal ? _breadCrumbLocalScroller : _breadCrumbRemoteScroller; | ||||
|     Future.delayed(Duration(milliseconds: 200), () { | ||||
|       controller.animateTo( | ||||
|           controller.position.maxScrollExtent, | ||||
|       controller.animateTo(controller.position.maxScrollExtent, | ||||
|           duration: Duration(milliseconds: 200), | ||||
|           curve: Curves.fastLinearToSlowEaseIn); | ||||
|     }); | ||||
| @ -332,7 +416,10 @@ class _FileManagerPageState extends State<FileManagerPage> | ||||
|               model.openDirectory(path, isLocal: isLocal); | ||||
|             }, isLocal), | ||||
|             divider: Icon(Icons.chevron_right), | ||||
|             overflow: ScrollableOverflow(controller: isLocal ? _breadCrumbLocalScroller : _breadCrumbRemoteScroller), | ||||
|             overflow: ScrollableOverflow( | ||||
|                 controller: isLocal | ||||
|                     ? _breadCrumbLocalScroller | ||||
|                     : _breadCrumbRemoteScroller), | ||||
|           )), | ||||
|           Row( | ||||
|             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( | ||||
|       height: 100, | ||||
|       child: Column( | ||||
| @ -370,14 +458,14 @@ class _FileManagerPageState extends State<FileManagerPage> | ||||
|           Padding( | ||||
|             padding: EdgeInsets.fromLTRB(30, 5, 30, 0), | ||||
|             child: Text( | ||||
|               model.currentDir.path, | ||||
|               dir.path, | ||||
|               style: TextStyle(color: MyTheme.darkGray), | ||||
|             ), | ||||
|           ), | ||||
|           Padding( | ||||
|             padding: EdgeInsets.all(2), | ||||
|             child: Text( | ||||
|               "${translate("Total")}: ${model.currentDir.entries.length} ${translate("items")}", | ||||
|               "${translate("Total")}: ${dir.entries.length} ${translate("items")}", | ||||
|               style: TextStyle(color: MyTheme.darkGray), | ||||
|             ), | ||||
|           ) | ||||
| @ -466,8 +554,8 @@ class _FileManagerPageState extends State<FileManagerPage> | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   List<BreadCrumbItem> getPathBreadCrumbItems( | ||||
|       void Function() onHome, void Function(List<String>) onPressed, bool isLocal) { | ||||
|   List<BreadCrumbItem> getPathBreadCrumbItems(void Function() onHome, | ||||
|       void Function(List<String>) onPressed, bool isLocal) { | ||||
|     final path = model.shortPath(isLocal); | ||||
|     final list = PathUtil.split(path, model.currentIsWindows); | ||||
|     final breadCrumbList = [ | ||||
|  | ||||
| @ -170,19 +170,18 @@ class FileModel extends ChangeNotifier { | ||||
|     if (false == resp) { | ||||
|       cancelJob(int.tryParse(evt['id']) ?? 0); | ||||
|     } else { | ||||
|       var msg = Map() | ||||
|         ..['id'] = evt['id'] | ||||
|         ..['file_num'] = evt['file_num'] | ||||
|         ..['is_upload'] = evt['is_upload'] | ||||
|         ..['remember'] = fileConfirmCheckboxRemember.toString(); | ||||
|       var need_override = false; | ||||
|       if (resp == null) { | ||||
|         // skip | ||||
|         msg['need_override'] = 'false'; | ||||
|         need_override = false; | ||||
|       } else { | ||||
|         // 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 { | ||||
|     _localOption.home = _ffi.target?.getByName("get_home_dir") ?? ""; | ||||
|     _localOption.showHidden = | ||||
|         _ffi.target?.getByName("peer_option", "local_show_hidden").isNotEmpty ?? | ||||
|             false; | ||||
|     _localOption.showHidden = (await _ffi.target?.bind.sessionGetPeerOption | ||||
|       (id: _ffi.target?.id ?? "", name: "local_show_hidden"))?.isNotEmpty ?? false; | ||||
| 
 | ||||
|     _remoteOption.showHidden = _ffi.target | ||||
|             ?.getByName("peer_option", "remote_show_hidden") | ||||
|             .isNotEmpty ?? | ||||
|         false; | ||||
|     _remoteOption.showHidden = (await _ffi.target?.bind.sessionGetPeerOption | ||||
|       (id: _ffi.target?.id ?? "", name: "remote_show_hidden"))?.isNotEmpty ?? false; | ||||
|     _remoteOption.isWindows = _ffi.target?.ffiModel.pi.platform == "Windows"; | ||||
| 
 | ||||
|     debugPrint("remote platform: ${_ffi.target?.ffiModel.pi.platform}"); | ||||
| 
 | ||||
|     await Future.delayed(Duration(milliseconds: 100)); | ||||
| 
 | ||||
|     final local = _ffi.target?.getByName("peer_option", "local_dir") ?? ""; | ||||
|     final remote = _ffi.target?.getByName("peer_option", "remote_dir") ?? ""; | ||||
|     final local = (await _ffi.target?.bind.sessionGetPeerOption | ||||
|       (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(remote.isEmpty ? _remoteOption.home : remote, isLocal: false); | ||||
|     await Future.delayed(Duration(seconds: 1)); | ||||
| @ -224,23 +222,16 @@ class FileModel extends ChangeNotifier { | ||||
|     SmartDialog.dismiss(); | ||||
| 
 | ||||
|     // save config | ||||
|     Map<String, String> msg = Map(); | ||||
|     Map<String, String> msgMap = Map(); | ||||
| 
 | ||||
|     msg["name"] = "local_dir"; | ||||
|     msg["value"] = _currentLocalDir.path; | ||||
|     _ffi.target?.setByName('peer_option', jsonEncode(msg)); | ||||
| 
 | ||||
|     msg["name"] = "local_show_hidden"; | ||||
|     msg["value"] = _localOption.showHidden ? "Y" : ""; | ||||
|     _ffi.target?.setByName('peer_option', jsonEncode(msg)); | ||||
| 
 | ||||
|     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)); | ||||
|     msgMap["local_dir"] = _currentLocalDir.path; | ||||
|     msgMap["local_show_hidden"] = _localOption.showHidden ? "Y" : ""; | ||||
|     msgMap["remote_dir"] = _currentRemoteDir.path; | ||||
|     msgMap["remote_show_hidden"] = _remoteOption.showHidden ? "Y" : ""; | ||||
|     final id = _ffi.target?.id ?? ""; | ||||
|     for(final msg in msgMap.entries) { | ||||
|       _ffi.target?.bind.sessionPeerOption(id: id, name: msg.key, value: msg.value); | ||||
|     } | ||||
|     _currentLocalDir.clear(); | ||||
|     _currentRemoteDir.clear(); | ||||
|     _localOption.clear(); | ||||
| @ -285,7 +276,23 @@ class FileModel extends ChangeNotifier { | ||||
|     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) { | ||||
|         debugPrint("Failed to sendFiles ,wrong path state"); | ||||
|         return; | ||||
| @ -303,6 +310,7 @@ class FileModel extends ChangeNotifier { | ||||
|             ,fileNum: 0, includeHidden: showHidden, isRemote: !(items.isLocal!)); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   bool removeCheckboxRemember = false; | ||||
| 
 | ||||
| @ -583,7 +591,7 @@ class FileFetcher { | ||||
|   } | ||||
| 
 | ||||
|   // 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) { | ||||
|     // 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 { | ||||
|     // TODO test Recursive is show hidden default? | ||||
|     try { | ||||
|       final msg = { | ||||
|         "id": id.toString(), | ||||
|         "path": path, | ||||
|         "show_hidden": showHidden.toString(), | ||||
|         "is_remote": (!isLocal).toString() | ||||
|       }; | ||||
|       // TODO | ||||
|       _ffi.setByName("read_dir_recursive", jsonEncode(msg)); | ||||
|       await _ffi.bind.sessionReadDirRecursive(id: _ffi.id, actId: id, path: path, isRemote: !isLocal, showHidden: showHidden); | ||||
|       return registerReadRecursiveTask(id); | ||||
|     } catch (e) { | ||||
|       return Future.error(e); | ||||
|  | ||||
| @ -15,6 +15,7 @@ use magnum_opus::{Channels::*, Decoder as AudioDecoder}; | ||||
| use sha2::{Digest, Sha256}; | ||||
| use uuid::Uuid; | ||||
| 
 | ||||
| pub use file_trait::FileManager; | ||||
| use hbb_common::{ | ||||
|     allow_err, | ||||
|     anyhow::{anyhow, Context}, | ||||
| @ -30,13 +31,14 @@ use hbb_common::{ | ||||
|     tokio::time::Duration, | ||||
|     AddrMangle, ResultType, Stream, | ||||
| }; | ||||
| pub use helper::LatencyController; | ||||
| use scrap::{Decoder, Image, VideoCodecId}; | ||||
| 
 | ||||
| pub use super::lang::*; | ||||
| 
 | ||||
| pub mod file_trait; | ||||
| pub use file_trait::FileManager; | ||||
| pub mod helper; | ||||
| pub use helper::LatencyController; | ||||
| 
 | ||||
| pub const SEC30: Duration = Duration::from_secs(30); | ||||
| 
 | ||||
| /// Client of the remote desktop.
 | ||||
| @ -1535,7 +1537,7 @@ pub enum Data { | ||||
|     Login((String, bool)), | ||||
|     Message(Message), | ||||
|     SendFiles((i32, String, String, i32, bool, bool)), | ||||
|     RemoveDirAll((i32, String, bool)), | ||||
|     RemoveDirAll((i32, String, bool, bool)), | ||||
|     ConfirmDeleteFiles((i32, i32)), | ||||
|     SetNoConfirm(i32), | ||||
|     RemoveDir((i32, String)), | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| use super::{Data, Interface}; | ||||
| use hbb_common::{fs, message_proto::*}; | ||||
| 
 | ||||
| use super::{Data, Interface}; | ||||
| 
 | ||||
| pub trait FileManager: Interface { | ||||
|     fn get_home_dir(&self) -> String { | ||||
|         fs::get_home_as_string() | ||||
| @ -48,8 +49,8 @@ pub trait FileManager: Interface { | ||||
|         self.send(Data::RemoveFile((id, path, file_num, is_remote))); | ||||
|     } | ||||
| 
 | ||||
|     fn remove_dir_all(&self, id: i32, path: String, is_remote: bool) { | ||||
|         self.send(Data::RemoveDirAll((id, path, is_remote))); | ||||
|     fn remove_dir_all(&self, id: i32, path: String, is_remote: bool, include_hidden: bool) { | ||||
|         self.send(Data::RemoveDirAll((id, path, is_remote, include_hidden))); | ||||
|     } | ||||
| 
 | ||||
|     fn confirm_delete_files(&self, id: i32, file_num: i32) { | ||||
|  | ||||
| @ -1,6 +1,10 @@ | ||||
| use crate::common::make_fd_to_json; | ||||
| use crate::{client::*, flutter_ffi::EventToUI}; | ||||
| use std::{ | ||||
|     collections::{HashMap, VecDeque}, | ||||
|     sync::{Arc, Mutex, RwLock}, | ||||
| }; | ||||
| 
 | ||||
| use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer}; | ||||
| 
 | ||||
| use hbb_common::{ | ||||
|     allow_err, | ||||
|     compress::decompress, | ||||
| @ -21,10 +25,9 @@ use hbb_common::{ | ||||
|     }, | ||||
|     Stream, | ||||
| }; | ||||
| use std::{ | ||||
|     collections::{HashMap, VecDeque}, | ||||
|     sync::{Arc, Mutex, RwLock}, | ||||
| }; | ||||
| 
 | ||||
| use crate::common::make_fd_to_json; | ||||
| use crate::{client::*, flutter_ffi::EventToUI}; | ||||
| 
 | ||||
| lazy_static::lazy_static! { | ||||
|     // 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]+
 | ||||
|     /// * `is_file_transfer` - If the session is used for file transfer.
 | ||||
|     pub fn start(identifier: &str, is_file_transfer: bool, events2ui: StreamSink<EventToUI>) { | ||||
|         LocalConfig::set_remote_id(&identifier); | ||||
|         // TODO check same id
 | ||||
|         let session_id = get_session_id(identifier.to_owned()); | ||||
|         LocalConfig::set_remote_id(&session_id); | ||||
|         // TODO close
 | ||||
|         // Self::close();
 | ||||
|         let events2ui = Arc::new(RwLock::new(events2ui)); | ||||
| @ -502,7 +505,11 @@ impl Interface for Session { | ||||
| 
 | ||||
|         if lc.is_file_transfer { | ||||
|             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; | ||||
|             } | ||||
|         } else { | ||||
| @ -992,20 +999,20 @@ impl Connection { | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Data::RemoveDirAll((id, path, is_remote)) => { | ||||
|             Data::RemoveDirAll((id, path, is_remote, include_hidden)) => { | ||||
|                 if is_remote { | ||||
|                     let mut msg_out = Message::new(); | ||||
|                     let mut file_action = FileAction::new(); | ||||
|                     file_action.set_all_files(ReadAllFiles { | ||||
|                         id, | ||||
|                         path: path.clone(), | ||||
|                         include_hidden: true, | ||||
|                         include_hidden, | ||||
|                         ..Default::default() | ||||
|                     }); | ||||
|                     msg_out.set_file_action(file_action); | ||||
|                     allow_err!(peer.send(&msg_out).await); | ||||
|                 } else { | ||||
|                     match fs::get_recursive_files(&path, true) { | ||||
|                     match fs::get_recursive_files(&path, include_hidden) { | ||||
|                         Ok(entries) => { | ||||
|                             let mut fd = FileDirectory::new(); | ||||
|                             fd.id = id; | ||||
| @ -1235,9 +1242,8 @@ pub mod connection_manager { | ||||
|         sync::{Mutex, RwLock}, | ||||
|     }; | ||||
| 
 | ||||
|     use crate::ipc; | ||||
|     use crate::ipc::Data; | ||||
|     use crate::server::Connection as Conn; | ||||
|     use serde_derive::Serialize; | ||||
| 
 | ||||
|     use hbb_common::{ | ||||
|         allow_err, | ||||
|         config::Config, | ||||
| @ -1254,7 +1260,10 @@ pub mod connection_manager { | ||||
|     }; | ||||
|     #[cfg(any(target_os = "android"))] | ||||
|     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; | ||||
| 
 | ||||
|  | ||||
| @ -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::common::make_fd_to_json; | ||||
| use crate::flutter::connection_manager::{self, get_clients_length, get_clients_state}; | ||||
| use crate::flutter::{self, Session, SESSIONS}; | ||||
| use crate::start_server; | ||||
| 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) { | ||||
|     *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) { | ||||
|     if let Some(session) = SESSIONS.read().unwrap().get(&id) { | ||||
|         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) { | ||||
|         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")] | ||||
| pub mod server_side { | ||||
|     use hbb_common::{config::Config, log}; | ||||
|     use jni::{ | ||||
|         objects::{JClass, JString}, | ||||
|         sys::jstring, | ||||
|         JNIEnv, | ||||
|     }; | ||||
| 
 | ||||
|     use hbb_common::{config::Config, log}; | ||||
| 
 | ||||
|     use crate::start_server; | ||||
| 
 | ||||
|     #[no_mangle] | ||||
|  | ||||
| @ -188,7 +188,8 @@ class JobTable: Reactor.Component { | ||||
|         job.confirmed = true; | ||||
|         return; | ||||
|       }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; | ||||
|         return; | ||||
|       } | ||||
|  | ||||
| @ -201,7 +201,7 @@ impl sciter::EventHandler for Handler { | ||||
|         fn read_remote_dir(String, bool); | ||||
|         fn send_chat(String); | ||||
|         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 set_no_confirm(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); | ||||
|                 if is_remote { | ||||
|                     let mut msg_out = Message::new(); | ||||
| @ -1801,7 +1801,7 @@ impl Remote { | ||||
|                     file_action.set_all_files(ReadAllFiles { | ||||
|                         id, | ||||
|                         path: path.clone(), | ||||
|                         include_hidden: true, | ||||
|                         include_hidden, | ||||
|                         ..Default::default() | ||||
|                     }); | ||||
|                     msg_out.set_file_action(file_action); | ||||
| @ -1809,7 +1809,7 @@ impl Remote { | ||||
|                     self.remove_jobs | ||||
|                         .insert(id, RemoveJob::new(Vec::new(), path, sep, is_remote)); | ||||
|                 } else { | ||||
|                     match fs::get_recursive_files(&path, true) { | ||||
|                     match fs::get_recursive_files(&path, include_hidden) { | ||||
|                         Ok(entries) => { | ||||
|                             let m = make_fd(id, &entries, true); | ||||
|                             self.handler.call("updateFolderFiles", &make_args!(m)); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user