Merge pull request #829 from Kingtous/flutter_desktop
feat: file transfer - initial migration to rust bridge
This commit is contained in:
commit
c1cf9307ac
@ -8,7 +8,6 @@ import 'package:flutter_hbb/models/file_model.dart';
|
|||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:toggle_switch/toggle_switch.dart';
|
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
@ -36,14 +35,13 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
Get.put(FFI(), tag: 'ft_${widget.id}');
|
Get.put(FFI.newFFI()..connect(widget.id, isFileTransfer: true),
|
||||||
_ffi.ffiModel.platformFFI = gFFI.ffiModel.platformFFI;
|
tag: 'ft_${widget.id}');
|
||||||
|
// _ffi.ffiModel.updateEventListener(widget.id);
|
||||||
_ffi.connect(widget.id, isFileTransfer: true);
|
|
||||||
_ffi.ffiModel.updateEventListener(widget.id);
|
|
||||||
if (!Platform.isLinux) {
|
if (!Platform.isLinux) {
|
||||||
Wakelock.enable();
|
Wakelock.enable();
|
||||||
}
|
}
|
||||||
|
print("init success with id ${widget.id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -80,24 +78,24 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
IconButton(icon: Icon(Icons.close), onPressed: clientClose),
|
IconButton(icon: Icon(Icons.close), onPressed: clientClose),
|
||||||
]),
|
]),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: ToggleSwitch(
|
// title: ToggleSwitch(
|
||||||
initialLabelIndex: model.isLocal ? 0 : 1,
|
// initialLabelIndex: model.isLocal ? 0 : 1,
|
||||||
activeBgColor: [MyTheme.idColor],
|
// activeBgColor: [MyTheme.idColor],
|
||||||
inactiveBgColor: MyTheme.grayBg,
|
// inactiveBgColor: MyTheme.grayBg,
|
||||||
inactiveFgColor: Colors.black54,
|
// inactiveFgColor: Colors.black54,
|
||||||
totalSwitches: 2,
|
// totalSwitches: 2,
|
||||||
minWidth: 100,
|
// minWidth: 100,
|
||||||
fontSize: 15,
|
// fontSize: 15,
|
||||||
iconSize: 18,
|
// iconSize: 18,
|
||||||
labels: [translate("Local"), translate("Remote")],
|
// labels: [translate("Local"), translate("Remote")],
|
||||||
icons: [Icons.phone_android_sharp, Icons.screen_share],
|
// icons: [Icons.phone_android_sharp, Icons.screen_share],
|
||||||
onToggle: (index) {
|
// onToggle: (index) {
|
||||||
final current = model.isLocal ? 0 : 1;
|
// final current = model.isLocal ? 0 : 1;
|
||||||
if (index != current) {
|
// if (index != current) {
|
||||||
model.togglePage();
|
// model.togglePage();
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
actions: [
|
actions: [
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
icon: Icon(Icons.more_vert),
|
icon: Icon(Icons.more_vert),
|
||||||
@ -197,7 +195,12 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: body(),
|
body: Row(
|
||||||
|
children: [
|
||||||
|
Flexible(flex: 1, child: body(isLocal: true)),
|
||||||
|
Flexible(flex: 1, child: body(isLocal: false))
|
||||||
|
],
|
||||||
|
),
|
||||||
bottomSheet: bottomSheet(),
|
bottomSheet: bottomSheet(),
|
||||||
));
|
));
|
||||||
}));
|
}));
|
||||||
@ -210,9 +213,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
return !_selectedItems.isOtherPage(model.isLocal);
|
return !_selectedItems.isOtherPage(model.isLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget body() {
|
Widget body({bool isLocal = false}) {
|
||||||
final isLocal = model.isLocal;
|
final fd = isLocal ? model.currentLocalDir : model.currentRemoteDir;
|
||||||
final fd = model.currentDir;
|
|
||||||
final entries = fd.entries;
|
final entries = fd.entries;
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
headTools(),
|
headTools(),
|
||||||
|
@ -233,7 +233,12 @@ class FileModel extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
openDirectory(currentDir.path);
|
if (isDesktop) {
|
||||||
|
openDirectory(currentRemoteDir.path);
|
||||||
|
openDirectory(currentLocalDir.path);
|
||||||
|
} else {
|
||||||
|
openDirectory(currentDir.path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openDirectory(String path, {bool? isLocal}) async {
|
openDirectory(String path, {bool? isLocal}) async {
|
||||||
@ -519,6 +524,10 @@ class FileModel extends ChangeNotifier {
|
|||||||
_currentRemoteDir.changeSortStyle(sort);
|
_currentRemoteDir.changeSortStyle(sort);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initFileFetcher() {
|
||||||
|
_fileFetcher.id = _ffi.target?.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class JobResultListener<T> {
|
class JobResultListener<T> {
|
||||||
@ -566,6 +575,17 @@ class FileFetcher {
|
|||||||
Map<String, Completer<FileDirectory>> remoteTasks = Map();
|
Map<String, Completer<FileDirectory>> remoteTasks = Map();
|
||||||
Map<int, Completer<FileDirectory>> readRecursiveTasks = Map();
|
Map<int, Completer<FileDirectory>> readRecursiveTasks = Map();
|
||||||
|
|
||||||
|
String? _id;
|
||||||
|
|
||||||
|
String? get id => _id;
|
||||||
|
|
||||||
|
set id(String? id) {
|
||||||
|
_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if id == null, means to fetch global FFI
|
||||||
|
FFI get _ffi => ffi(_id == null ? "" : 'ft_${_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
|
||||||
final tasks = remoteTasks; // bypass now
|
final tasks = remoteTasks; // bypass now
|
||||||
@ -633,13 +653,14 @@ class FileFetcher {
|
|||||||
Future<FileDirectory> fetchDirectory(
|
Future<FileDirectory> fetchDirectory(
|
||||||
String path, bool isLocal, bool showHidden) async {
|
String path, bool isLocal, bool showHidden) async {
|
||||||
try {
|
try {
|
||||||
final msg = {"path": path, "show_hidden": showHidden.toString()};
|
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
final res = gFFI.getByName("read_local_dir_sync", jsonEncode(msg));
|
final res = await _ffi.bind.sessionReadLocalDirSync(
|
||||||
|
id: id ?? "", path: path, showHidden: showHidden);
|
||||||
final fd = FileDirectory.fromJson(jsonDecode(res));
|
final fd = FileDirectory.fromJson(jsonDecode(res));
|
||||||
return fd;
|
return fd;
|
||||||
} else {
|
} else {
|
||||||
gFFI.setByName("read_remote_dir", jsonEncode(msg));
|
await _ffi.bind.sessionReadRemoteDir(
|
||||||
|
id: id ?? "", path: path, includeHidden: showHidden);
|
||||||
return registerReadTask(isLocal, path);
|
return registerReadTask(isLocal, path);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -657,7 +678,8 @@ class FileFetcher {
|
|||||||
"show_hidden": showHidden.toString(),
|
"show_hidden": showHidden.toString(),
|
||||||
"is_remote": (!isLocal).toString()
|
"is_remote": (!isLocal).toString()
|
||||||
};
|
};
|
||||||
gFFI.setByName("read_dir_recursive", jsonEncode(msg));
|
// 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);
|
||||||
|
@ -784,6 +784,13 @@ class FFI {
|
|||||||
this.fileModel = FileModel(WeakReference(this));
|
this.fileModel = FileModel(WeakReference(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static FFI newFFI() {
|
||||||
|
final ffi = FFI();
|
||||||
|
// keep platformFFI only once
|
||||||
|
ffi.ffiModel.platformFFI = gFFI.ffiModel.platformFFI;
|
||||||
|
return ffi;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the remote id for current client.
|
/// Get the remote id for current client.
|
||||||
String getId() {
|
String getId() {
|
||||||
return getByName('remote_id'); // TODO
|
return getByName('remote_id'); // TODO
|
||||||
@ -888,32 +895,32 @@ class FFI {
|
|||||||
setByName('connect_file_transfer', id);
|
setByName('connect_file_transfer', id);
|
||||||
} else {
|
} else {
|
||||||
chatModel.resetClientMode();
|
chatModel.resetClientMode();
|
||||||
// setByName('connect', id);
|
|
||||||
// TODO multi model instances
|
|
||||||
canvasModel.id = id;
|
canvasModel.id = id;
|
||||||
imageModel._id = id;
|
imageModel._id = id;
|
||||||
cursorModel.id = id;
|
cursorModel.id = id;
|
||||||
final stream =
|
|
||||||
bind.sessionConnect(id: id, isFileTransfer: isFileTransfer);
|
|
||||||
final cb = ffiModel.startEventListener(id);
|
|
||||||
() async {
|
|
||||||
await for (final message in stream) {
|
|
||||||
if (message is Event) {
|
|
||||||
try {
|
|
||||||
debugPrint("event:${message.field0}");
|
|
||||||
Map<String, dynamic> event = json.decode(message.field0);
|
|
||||||
cb(event);
|
|
||||||
} catch (e) {
|
|
||||||
print('json.decode fail(): $e');
|
|
||||||
}
|
|
||||||
} else if (message is Rgba) {
|
|
||||||
imageModel.onRgba(message.field0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
// every instance will bind a stream
|
|
||||||
}
|
}
|
||||||
|
final stream = bind.sessionConnect(id: id, isFileTransfer: isFileTransfer);
|
||||||
|
final cb = ffiModel.startEventListener(id);
|
||||||
|
() async {
|
||||||
|
await for (final message in stream) {
|
||||||
|
if (message is Event) {
|
||||||
|
try {
|
||||||
|
debugPrint("event:${message.field0}");
|
||||||
|
Map<String, dynamic> event = json.decode(message.field0);
|
||||||
|
cb(event);
|
||||||
|
} catch (e) {
|
||||||
|
print('json.decode fail(): $e');
|
||||||
|
}
|
||||||
|
} else if (message is Rgba) {
|
||||||
|
imageModel.onRgba(message.field0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
// every instance will bind a stream
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
if (isFileTransfer) {
|
||||||
|
this.fileModel.initFileFetcher();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Login with [password], choose if the client should [remember] it.
|
/// Login with [password], choose if the client should [remember] it.
|
||||||
|
@ -93,7 +93,12 @@ class PlatformFFI {
|
|||||||
_ffiBind = RustdeskImpl(dylib);
|
_ffiBind = RustdeskImpl(dylib);
|
||||||
_startListenEvent(_ffiBind); // global event
|
_startListenEvent(_ffiBind); // global event
|
||||||
try {
|
try {
|
||||||
_homeDir = (await ExternalPath.getExternalStorageDirectories())[0];
|
if (isAndroid) {
|
||||||
|
// only support for android
|
||||||
|
_homeDir = (await ExternalPath.getExternalStorageDirectories())[0];
|
||||||
|
} else {
|
||||||
|
_homeDir = (await getDownloadsDirectory())?.path ?? "";
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ static void my_application_activate(GApplication* application) {
|
|||||||
|
|
||||||
auto bdw = bitsdojo_window_from(window); // <--- add this line
|
auto bdw = bitsdojo_window_from(window); // <--- add this line
|
||||||
bdw->setCustomFrame(true); // <-- add this line
|
bdw->setCustomFrame(true); // <-- add this line
|
||||||
//gtk_window_set_default_size(window, 1280, 720); // <-- comment this line
|
gtk_window_set_default_size(window, 1280, 720); // <-- comment this line
|
||||||
gtk_widget_show(GTK_WIDGET(window));
|
gtk_widget_show(GTK_WIDGET(window));
|
||||||
|
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
|
@ -149,6 +149,11 @@ void wire_session_create_dir(int64_t port_,
|
|||||||
struct wire_uint_8_list *path,
|
struct wire_uint_8_list *path,
|
||||||
bool is_remote);
|
bool is_remote);
|
||||||
|
|
||||||
|
void wire_session_read_local_dir_sync(int64_t port_,
|
||||||
|
struct wire_uint_8_list *id,
|
||||||
|
struct wire_uint_8_list *path,
|
||||||
|
bool show_hidden);
|
||||||
|
|
||||||
struct wire_uint_8_list *new_uint_8_list(int32_t len);
|
struct wire_uint_8_list *new_uint_8_list(int32_t len);
|
||||||
|
|
||||||
void free_WireSyncReturnStruct(struct WireSyncReturnStruct val);
|
void free_WireSyncReturnStruct(struct WireSyncReturnStruct val);
|
||||||
@ -194,6 +199,7 @@ static int64_t dummy_method_to_enforce_bundling(void) {
|
|||||||
dummy_var ^= ((int64_t) (void*) wire_session_remove_all_empty_dirs);
|
dummy_var ^= ((int64_t) (void*) wire_session_remove_all_empty_dirs);
|
||||||
dummy_var ^= ((int64_t) (void*) wire_session_cancel_job);
|
dummy_var ^= ((int64_t) (void*) wire_session_cancel_job);
|
||||||
dummy_var ^= ((int64_t) (void*) wire_session_create_dir);
|
dummy_var ^= ((int64_t) (void*) wire_session_create_dir);
|
||||||
|
dummy_var ^= ((int64_t) (void*) wire_session_read_local_dir_sync);
|
||||||
dummy_var ^= ((int64_t) (void*) new_uint_8_list);
|
dummy_var ^= ((int64_t) (void*) new_uint_8_list);
|
||||||
dummy_var ^= ((int64_t) (void*) free_WireSyncReturnStruct);
|
dummy_var ^= ((int64_t) (void*) free_WireSyncReturnStruct);
|
||||||
dummy_var ^= ((int64_t) (void*) store_dart_post_cobject);
|
dummy_var ^= ((int64_t) (void*) store_dart_post_cobject);
|
||||||
|
@ -24,10 +24,9 @@ lazy_static::lazy_static! {
|
|||||||
pub static ref SOFTWARE_UPDATE_URL: Arc<Mutex<String>> = Default::default();
|
pub static ref SOFTWARE_UPDATE_URL: Arc<Mutex<String>> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref MOBILE_INFO1: Arc<Mutex<String>> = Default::default();
|
pub static ref FLUTTER_INFO1: Arc<Mutex<String>> = Default::default();
|
||||||
pub static ref MOBILE_INFO2: Arc<Mutex<String>> = Default::default();
|
pub static ref FLUTTER_INFO2: Arc<Mutex<String>> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -307,6 +307,15 @@ pub fn session_create_dir(id: String, act_id: i32, path: String, is_remote: bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_read_local_dir_sync(id: String, path: String, show_hidden: bool) -> String {
|
||||||
|
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||||
|
if let Ok(fd) = fs::read_dir(&fs::get_path(&path), show_hidden) {
|
||||||
|
return make_fd_to_json(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
/// FFI for **get** commands which are idempotent.
|
/// FFI for **get** commands which are idempotent.
|
||||||
/// Return result in c string.
|
/// Return result in c string.
|
||||||
///
|
///
|
||||||
@ -397,21 +406,21 @@ unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *co
|
|||||||
"get_home_dir" => {
|
"get_home_dir" => {
|
||||||
res = fs::get_home_as_string();
|
res = fs::get_home_as_string();
|
||||||
}
|
}
|
||||||
"read_local_dir_sync" => {
|
// "read_local_dir_sync" => {
|
||||||
if let Ok(value) = arg.to_str() {
|
// if let Ok(value) = arg.to_str() {
|
||||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||||
if let (Some(path), Some(show_hidden)) =
|
// if let (Some(path), Some(show_hidden)) =
|
||||||
(m.get("path"), m.get("show_hidden"))
|
// (m.get("path"), m.get("show_hidden"))
|
||||||
{
|
// {
|
||||||
if let Ok(fd) =
|
// if let Ok(fd) =
|
||||||
fs::read_dir(&fs::get_path(path), show_hidden.eq("true"))
|
// fs::read_dir(&fs::get_path(path), show_hidden.eq("true"))
|
||||||
{
|
// {
|
||||||
res = make_fd_to_json(fd);
|
// res = make_fd_to_json(fd);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// Server Side
|
// Server Side
|
||||||
#[cfg(not(any(target_os = "ios")))]
|
#[cfg(not(any(target_os = "ios")))]
|
||||||
"clients_state" => {
|
"clients_state" => {
|
||||||
@ -452,13 +461,11 @@ unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) {
|
|||||||
"init" => {
|
"init" => {
|
||||||
initialize(value);
|
initialize(value);
|
||||||
}
|
}
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
||||||
"info1" => {
|
"info1" => {
|
||||||
*crate::common::MOBILE_INFO1.lock().unwrap() = value.to_owned();
|
*crate::common::FLUTTER_INFO1.lock().unwrap() = value.to_owned();
|
||||||
}
|
}
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
||||||
"info2" => {
|
"info2" => {
|
||||||
*crate::common::MOBILE_INFO2.lock().unwrap() = value.to_owned();
|
*crate::common::FLUTTER_INFO2.lock().unwrap() = value.to_owned();
|
||||||
}
|
}
|
||||||
// "connect" => {
|
// "connect" => {
|
||||||
// Session::start(value, false);
|
// Session::start(value, false);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user