mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-27 14:59:02 +08:00
feat: Add empty dirs on sendfiles
This commit is contained in:
parent
8b710f62c8
commit
f6f419761d
@ -100,6 +100,10 @@ class FileModel {
|
||||
fileFetcher.tryCompleteTask(evt['value'], evt['is_local']);
|
||||
}
|
||||
|
||||
void receiveEmptyDirs(Map<String, dynamic> evt) {
|
||||
fileFetcher.tryCompleteEmptyDirsTask(evt['value'], evt['is_local']);
|
||||
}
|
||||
|
||||
Future<void> postOverrideFileConfirm(Map<String, dynamic> evt) async {
|
||||
evtLoop.pushEvent(
|
||||
_FileDialogEvent(WeakReference(this), FileDialogType.overwrite, evt));
|
||||
@ -470,7 +474,8 @@ class FileController {
|
||||
}
|
||||
|
||||
/// sendFiles from current side (FileController.isLocal) to other side (SelectedItems).
|
||||
void sendFiles(SelectedItems items, DirectoryData otherSideData) {
|
||||
Future<void> sendFiles(
|
||||
SelectedItems items, DirectoryData otherSideData) async {
|
||||
/// ignore wrong items side status
|
||||
if (items.isLocal != isLocal) {
|
||||
return;
|
||||
@ -496,6 +501,42 @@ class FileController {
|
||||
debugPrint(
|
||||
"path: ${from.path}, toPath: $toPath, to: ${PathUtil.join(toPath, from.name, isWindows)}");
|
||||
}
|
||||
|
||||
if (!isLocal &&
|
||||
versionCmp(rootState.target!.ffiModel.pi.version, '1.3.3') < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Entry> entrys = items.items.toList();
|
||||
var isRemote = isLocal == true ? true : false;
|
||||
|
||||
await Future.forEach(entrys, (Entry item) async {
|
||||
if (!item.isDirectory) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<String> paths = [];
|
||||
|
||||
final emptyDirs =
|
||||
await fileFetcher.readEmptyDirs(item.path, isLocal, showHidden);
|
||||
|
||||
if (emptyDirs.isEmpty) {
|
||||
return;
|
||||
} else {
|
||||
for (var dir in emptyDirs) {
|
||||
paths.add(dir.path);
|
||||
}
|
||||
}
|
||||
|
||||
final dirs = paths.map((path) {
|
||||
return PathUtil.getOtherSidePath(directory.value.path, path,
|
||||
options.value.isWindows, toPath, isWindows);
|
||||
});
|
||||
|
||||
for (var dir in dirs) {
|
||||
createDirWithRemote(dir, isRemote);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _removeCheckboxRemember = false;
|
||||
@ -689,12 +730,16 @@ class FileController {
|
||||
sessionId: sessionId, actId: actId, path: path, isRemote: !isLocal);
|
||||
}
|
||||
|
||||
Future<void> createDir(String path) async {
|
||||
Future<void> createDirWithRemote(String path, bool isRemote) async {
|
||||
bind.sessionCreateDir(
|
||||
sessionId: sessionId,
|
||||
actId: JobController.jobID.next(),
|
||||
path: path,
|
||||
isRemote: !isLocal);
|
||||
isRemote: isRemote);
|
||||
}
|
||||
|
||||
Future<void> createDir(String path) async {
|
||||
await createDirWithRemote(path, !isLocal);
|
||||
}
|
||||
|
||||
Future<void> renameAction(Entry item, bool isLocal) async {
|
||||
@ -1064,6 +1109,7 @@ class JobResultListener<T> {
|
||||
class FileFetcher {
|
||||
// Map<String,Completer<FileDirectory>> localTasks = {}; // now we only use read local dir sync
|
||||
Map<String, Completer<FileDirectory>> remoteTasks = {};
|
||||
Map<String, Completer<List<FileDirectory>>> remoteEmptyDirsTasks = {};
|
||||
Map<int, Completer<FileDirectory>> readRecursiveTasks = {};
|
||||
|
||||
final GetSessionID getSessionID;
|
||||
@ -1071,6 +1117,24 @@ class FileFetcher {
|
||||
|
||||
FileFetcher(this.getSessionID);
|
||||
|
||||
Future<List<FileDirectory>> registerReadEmptyDirsTask(
|
||||
bool isLocal, String path) {
|
||||
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
|
||||
final tasks = remoteEmptyDirsTasks; // bypass now
|
||||
if (tasks.containsKey(path)) {
|
||||
throw "Failed to registerReadEmptyDirsTask, already have same read job";
|
||||
}
|
||||
final c = Completer<List<FileDirectory>>();
|
||||
tasks[path] = c;
|
||||
|
||||
Timer(Duration(seconds: 2), () {
|
||||
tasks.remove(path);
|
||||
if (c.isCompleted) return;
|
||||
c.completeError("Failed to read empty dirs, timeout");
|
||||
});
|
||||
return c.future;
|
||||
}
|
||||
|
||||
Future<FileDirectory> registerReadTask(bool isLocal, String path) {
|
||||
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
|
||||
final tasks = remoteTasks; // bypass now
|
||||
@ -1104,6 +1168,25 @@ class FileFetcher {
|
||||
return c.future;
|
||||
}
|
||||
|
||||
tryCompleteEmptyDirsTask(String? msg, String? isLocalStr) {
|
||||
if (msg == null || isLocalStr == null) return;
|
||||
late final Map<String, Completer<List<FileDirectory>>> tasks;
|
||||
try {
|
||||
final map = jsonDecode(msg);
|
||||
final String path = map["path"];
|
||||
final List<dynamic> fdJsons = map["empty_dirs"];
|
||||
final List<FileDirectory> fds =
|
||||
fdJsons.map((fdJson) => FileDirectory.fromJson(fdJson)).toList();
|
||||
|
||||
tasks = remoteEmptyDirsTasks;
|
||||
final completer = tasks.remove(path);
|
||||
|
||||
completer?.complete(fds);
|
||||
} catch (e) {
|
||||
debugPrint("tryCompleteJob err: $e");
|
||||
}
|
||||
}
|
||||
|
||||
tryCompleteTask(String? msg, String? isLocalStr) {
|
||||
if (msg == null || isLocalStr == null) return;
|
||||
late final Map<Object, Completer<FileDirectory>> tasks;
|
||||
@ -1127,6 +1210,28 @@ class FileFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<FileDirectory>> readEmptyDirs(
|
||||
String path, bool isLocal, bool showHidden) async {
|
||||
try {
|
||||
if (isLocal) {
|
||||
final res = await bind.sessionReadLocalEmptyDirsRecursiveSync(
|
||||
sessionId: sessionId, path: path, includeHidden: showHidden);
|
||||
|
||||
final List<dynamic> fdJsons = jsonDecode(res);
|
||||
|
||||
final List<FileDirectory> fds =
|
||||
fdJsons.map((fdJson) => FileDirectory.fromJson(fdJson)).toList();
|
||||
return fds;
|
||||
} else {
|
||||
await bind.sessionReadRemoteEmptyDirsRecursiveSync(
|
||||
sessionId: sessionId, path: path, includeHidden: showHidden);
|
||||
return registerReadEmptyDirsTask(isLocal, path);
|
||||
}
|
||||
} catch (e) {
|
||||
return Future.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<FileDirectory> fetchDirectory(
|
||||
String path, bool isLocal, bool showHidden) async {
|
||||
try {
|
||||
@ -1373,6 +1478,24 @@ class PathUtil {
|
||||
static final windowsContext = path.Context(style: path.Style.windows);
|
||||
static final posixContext = path.Context(style: path.Style.posix);
|
||||
|
||||
static String getOtherSidePath(String mainRootPath, String mainPath,
|
||||
bool isMainWindows, String otherRootPath, bool isOtherWindows) {
|
||||
final mainPathUtil = isMainWindows ? windowsContext : posixContext;
|
||||
final relativePath = mainPathUtil.relative(mainPath, from: mainRootPath);
|
||||
|
||||
final names = mainPathUtil.split(relativePath);
|
||||
|
||||
final otherPathUtil = isOtherWindows ? windowsContext : posixContext;
|
||||
|
||||
String path = otherRootPath;
|
||||
|
||||
for (var name in names) {
|
||||
path = otherPathUtil.join(path, name);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static String join(String path1, String path2, bool isWindows) {
|
||||
final pathUtil = isWindows ? windowsContext : posixContext;
|
||||
return pathUtil.join(path1, path2);
|
||||
|
@ -309,6 +309,8 @@ class FfiModel with ChangeNotifier {
|
||||
.receive(int.parse(evt['id'] as String), evt['text'] ?? '');
|
||||
} else if (name == 'file_dir') {
|
||||
parent.target?.fileModel.receiveFileDir(evt);
|
||||
} else if (name == 'empty_dirs') {
|
||||
parent.target?.fileModel.receiveEmptyDirs(evt);
|
||||
} else if (name == 'job_progress') {
|
||||
parent.target?.fileModel.jobController.tryUpdateJobProgress(evt);
|
||||
} else if (name == 'job_done') {
|
||||
|
@ -368,6 +368,16 @@ message ReadDir {
|
||||
bool include_hidden = 2;
|
||||
}
|
||||
|
||||
message ReadEmptyDirs {
|
||||
string path = 1;
|
||||
bool include_hidden = 2;
|
||||
}
|
||||
|
||||
message ReadEmptyDirsResponse {
|
||||
string path = 1;
|
||||
repeated FileDirectory empty_dirs = 2;
|
||||
}
|
||||
|
||||
message ReadAllFiles {
|
||||
int32 id = 1;
|
||||
string path = 2;
|
||||
@ -392,6 +402,7 @@ message FileAction {
|
||||
FileTransferCancel cancel = 8;
|
||||
FileTransferSendConfirmRequest send_confirm = 9;
|
||||
FileRename rename = 10;
|
||||
ReadEmptyDirs read_empty_dirs = 11;
|
||||
}
|
||||
}
|
||||
|
||||
@ -404,6 +415,7 @@ message FileResponse {
|
||||
FileTransferError error = 3;
|
||||
FileTransferDone done = 4;
|
||||
FileTransferDigest digest = 5;
|
||||
ReadEmptyDirsResponse empty_dirs = 6;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,6 +185,51 @@ pub fn get_recursive_files(path: &str, include_hidden: bool) -> ResultType<Vec<F
|
||||
read_dir_recursive(&get_path(path), &get_path(""), include_hidden)
|
||||
}
|
||||
|
||||
fn read_empty_dirs_recursive(
|
||||
path: &PathBuf,
|
||||
prefix: &Path,
|
||||
include_hidden: bool,
|
||||
) -> ResultType<Vec<FileDirectory>> {
|
||||
let mut dirs = Vec::new();
|
||||
if path.is_dir() {
|
||||
// to-do: symbol link handling, cp the link rather than the content
|
||||
// to-do: file mode, for unix
|
||||
let fd = read_dir(path, include_hidden)?;
|
||||
if fd.entries.is_empty() {
|
||||
dirs.push(fd);
|
||||
} else {
|
||||
for entry in fd.entries.iter() {
|
||||
match entry.entry_type.enum_value() {
|
||||
Ok(FileType::Dir) => {
|
||||
if let Ok(mut tmp) = read_empty_dirs_recursive(
|
||||
&path.join(&entry.name),
|
||||
&prefix.join(&entry.name),
|
||||
include_hidden,
|
||||
) {
|
||||
for entry in tmp.drain(0..) {
|
||||
dirs.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(dirs)
|
||||
} else if path.is_file() {
|
||||
Ok(dirs)
|
||||
} else {
|
||||
bail!("Not exists");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_empty_dirs_recursive(
|
||||
path: &str,
|
||||
include_hidden: bool,
|
||||
) -> ResultType<Vec<FileDirectory>> {
|
||||
read_empty_dirs_recursive(&get_path(path), &get_path(""), include_hidden)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_file_exists(file_path: &str) -> bool {
|
||||
return Path::new(file_path).exists();
|
||||
|
@ -43,6 +43,18 @@ pub trait FileManager: Interface {
|
||||
self.send(Data::CancelJob(id));
|
||||
}
|
||||
|
||||
fn read_empty_dirs(&self, path: String, include_hidden: bool) {
|
||||
let mut msg_out = Message::new();
|
||||
let mut file_action = FileAction::new();
|
||||
file_action.set_read_empty_dirs(ReadEmptyDirs {
|
||||
path,
|
||||
include_hidden,
|
||||
..Default::default()
|
||||
});
|
||||
msg_out.set_file_action(file_action);
|
||||
self.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
fn read_remote_dir(&self, path: String, include_hidden: bool) {
|
||||
let mut msg_out = Message::new();
|
||||
let mut file_action = FileAction::new();
|
||||
|
@ -1274,6 +1274,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
Some(message::Union::FileResponse(fr)) => {
|
||||
match fr.union {
|
||||
Some(file_response::Union::EmptyDirs(res)) => {
|
||||
self.handler.update_empty_dirs(res);
|
||||
}
|
||||
Some(file_response::Union::Dir(fd)) => {
|
||||
#[cfg(windows)]
|
||||
let entries = fd.entries.to_vec();
|
||||
|
@ -5,7 +5,7 @@ use std::{
|
||||
task::Poll,
|
||||
};
|
||||
|
||||
use serde_json::Value;
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
@ -837,7 +837,7 @@ async fn check_software_update_() -> hbb_common::ResultType<()> {
|
||||
let _ = crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, data);
|
||||
}
|
||||
}
|
||||
*SOFTWARE_UPDATE_URL.lock().unwrap() = response_url;
|
||||
*SOFTWARE_UPDATE_URL.lock().unwrap() = response_url;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -1051,6 +1051,11 @@ pub fn get_supported_keyboard_modes(version: i64, peer_platform: &str) -> Vec<Ke
|
||||
}
|
||||
|
||||
pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> String {
|
||||
let fd_json = _make_fd_to_json(id, path, entries);
|
||||
serde_json::to_string(&fd_json).unwrap_or("".into())
|
||||
}
|
||||
|
||||
pub fn _make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> Map<String, Value> {
|
||||
use serde_json::json;
|
||||
let mut fd_json = serde_json::Map::new();
|
||||
fd_json.insert("id".into(), json!(id));
|
||||
@ -1066,7 +1071,33 @@ pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> Strin
|
||||
entries_out.push(entry_map);
|
||||
}
|
||||
fd_json.insert("entries".into(), json!(entries_out));
|
||||
serde_json::to_string(&fd_json).unwrap_or("".into())
|
||||
fd_json
|
||||
}
|
||||
|
||||
pub fn make_vec_fd_to_json(fds: &[FileDirectory]) -> String {
|
||||
let mut fd_jsons = vec![];
|
||||
|
||||
for fd in fds.iter() {
|
||||
let fd_json = _make_fd_to_json(fd.id, fd.path.clone(), &fd.entries);
|
||||
fd_jsons.push(fd_json);
|
||||
}
|
||||
|
||||
serde_json::to_string(&fd_jsons).unwrap_or("".into())
|
||||
}
|
||||
|
||||
pub fn make_empty_dirs_response_to_json(res: &ReadEmptyDirsResponse) -> String {
|
||||
let mut map: Map<String, Value> = serde_json::Map::new();
|
||||
map.insert("path".into(), json!(res.path));
|
||||
|
||||
let mut fd_jsons = vec![];
|
||||
|
||||
for fd in res.empty_dirs.iter() {
|
||||
let fd_json = _make_fd_to_json(fd.id, fd.path.clone(), &fd.entries);
|
||||
fd_jsons.push(fd_json);
|
||||
}
|
||||
map.insert("empty_dirs".into(), fd_jsons.into());
|
||||
|
||||
serde_json::to_string(&map).unwrap_or("".into())
|
||||
}
|
||||
|
||||
/// The function to handle the url scheme sent by the system.
|
||||
|
@ -726,6 +726,20 @@ impl InvokeUiSession for FlutterHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_empty_dirs(&self, res: ReadEmptyDirsResponse) {
|
||||
self.push_event(
|
||||
"empty_dirs",
|
||||
&[
|
||||
("is_local", "false"),
|
||||
(
|
||||
"value",
|
||||
&crate::common::make_empty_dirs_response_to_json(&res),
|
||||
),
|
||||
],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
// unused in flutter
|
||||
fn update_transfer_list(&self) {}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
client::file_trait::FileManager,
|
||||
common::make_fd_to_json,
|
||||
common::{make_fd_to_json, make_vec_fd_to_json},
|
||||
flutter::{
|
||||
self, session_add, session_add_existed, session_start_, sessions, try_sync_peer_option,
|
||||
},
|
||||
@ -682,6 +682,27 @@ pub fn session_read_local_dir_sync(
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn session_read_local_empty_dirs_recursive_sync(
|
||||
_session_id: SessionID,
|
||||
path: String,
|
||||
include_hidden: bool,
|
||||
) -> String {
|
||||
if let Ok(fds) = fs::get_empty_dirs_recursive(&path, include_hidden) {
|
||||
return make_vec_fd_to_json(&fds);
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn session_read_remote_empty_dirs_recursive_sync(
|
||||
session_id: SessionID,
|
||||
path: String,
|
||||
include_hidden: bool,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.read_empty_dirs(path, include_hidden);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_platform(session_id: SessionID, is_remote: bool) -> String {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
return session.get_platform(is_remote);
|
||||
|
@ -45,6 +45,10 @@ pub static EXIT_RECV_CLOSE: AtomicBool = AtomicBool::new(true);
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum FS {
|
||||
ReadEmptyDirs {
|
||||
dir: String,
|
||||
include_hidden: bool,
|
||||
},
|
||||
ReadDir {
|
||||
dir: String,
|
||||
include_hidden: bool,
|
||||
|
@ -2149,6 +2149,9 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
match fa.union {
|
||||
Some(file_action::Union::ReadEmptyDirs(rd)) => {
|
||||
self.read_empty_dirs(&rd.path, rd.include_hidden);
|
||||
}
|
||||
Some(file_action::Union::ReadDir(rd)) => {
|
||||
self.read_dir(&rd.path, rd.include_hidden);
|
||||
}
|
||||
@ -3145,6 +3148,16 @@ impl Connection {
|
||||
raii::AuthedConnID::check_remove_session(self.inner.id(), self.session_key());
|
||||
}
|
||||
|
||||
fn read_empty_dirs(&mut self, dir: &str, include_hidden: bool) {
|
||||
log::debug!("empty_dirs path is: {}", dir);
|
||||
|
||||
let dir = dir.to_string();
|
||||
self.send_fs(ipc::FS::ReadEmptyDirs {
|
||||
dir,
|
||||
include_hidden,
|
||||
});
|
||||
}
|
||||
|
||||
fn read_dir(&mut self, dir: &str, include_hidden: bool) {
|
||||
let dir = dir.to_string();
|
||||
self.send_fs(ipc::FS::ReadDir {
|
||||
|
@ -751,6 +751,12 @@ async fn handle_fs(
|
||||
use hbb_common::fs::serialize_transfer_job;
|
||||
|
||||
match fs {
|
||||
ipc::FS::ReadEmptyDirs {
|
||||
dir,
|
||||
include_hidden,
|
||||
} => {
|
||||
read_empty_dirs(&dir, include_hidden, tx).await;
|
||||
}
|
||||
ipc::FS::ReadDir {
|
||||
dir,
|
||||
include_hidden,
|
||||
@ -907,6 +913,26 @@ async fn handle_fs(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
async fn read_empty_dirs(dir: &str, include_hidden: bool, tx: &UnboundedSender<Data>) {
|
||||
let path = dir.to_owned();
|
||||
let path_clone = dir.to_owned();
|
||||
|
||||
if let Ok(Ok(fds)) =
|
||||
spawn_blocking(move || fs::get_empty_dirs_recursive(&path, include_hidden)).await
|
||||
{
|
||||
let mut msg_out = Message::new();
|
||||
let mut file_response = FileResponse::new();
|
||||
file_response.set_empty_dirs(ReadEmptyDirsResponse {
|
||||
path: path_clone,
|
||||
empty_dirs: fds,
|
||||
..Default::default()
|
||||
});
|
||||
msg_out.set_file_response(file_response);
|
||||
send_raw(msg_out, tx);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
async fn read_dir(dir: &str, include_hidden: bool, tx: &UnboundedSender<Data>) {
|
||||
let path = {
|
||||
|
@ -1555,6 +1555,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
|
||||
#[cfg(feature = "flutter")]
|
||||
fn is_multi_ui_session(&self) -> bool;
|
||||
fn update_record_status(&self, start: bool);
|
||||
fn update_empty_dirs(&self, _res: ReadEmptyDirsResponse) {}
|
||||
}
|
||||
|
||||
impl<T: InvokeUiSession> Deref for Session<T> {
|
||||
|
Loading…
Reference in New Issue
Block a user