diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 9fba78a92..b4a87ad7f 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -245,6 +245,7 @@ message FileAction { oneof union { ReadDir read_dir = 1; FileTransferSendRequest send = 2; + FileTransferSendConfirmRequest send_confirm = 9; FileTransferReceiveRequest receive = 3; FileDirCreate create = 4; FileRemoveDir remove_dir = 5; @@ -262,14 +263,23 @@ message FileResponse { FileTransferBlock block = 2; FileTransferError error = 3; FileTransferDone done = 4; + FileTransferDigest digest = 5; } } +message FileTransferDigest { + int32 id = 1; + sint32 file_num = 2; + uint64 last_edit_timestamp = 3; + uint64 file_size = 4; +} + message FileTransferBlock { int32 id = 1; sint32 file_num = 2; bytes data = 3; bool compressed = 4; + uint32 blk_id = 5; } message FileTransferError { @@ -284,6 +294,15 @@ message FileTransferSendRequest { bool include_hidden = 3; } +message FileTransferSendConfirmRequest { + int32 id = 1; + sint32 file_num = 2; + oneof union { + bool skip = 3; + uint32 offset_blk = 4; + } +} + message FileTransferDone { int32 id = 1; sint32 file_num = 2; diff --git a/libs/hbb_common/src/fs.rs b/libs/hbb_common/src/fs.rs index 475f4dfc6..554330ab2 100644 --- a/libs/hbb_common/src/fs.rs +++ b/libs/hbb_common/src/fs.rs @@ -5,8 +5,10 @@ use crate::{ compress::{compress, decompress}, config::{Config, COMPRESS_LEVEL}, }; +use log::log; #[cfg(windows)] use std::os::windows::prelude::*; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::{fs::File, io::*}; pub fn read_dir(path: &PathBuf, include_hidden: bool) -> ResultType { @@ -184,6 +186,11 @@ pub fn get_recursive_files(path: &str, include_hidden: bool) -> ResultType bool { + return Path::new(file_path).exists(); +} + #[derive(Default)] pub struct TransferJob { id: i32, @@ -194,6 +201,7 @@ pub struct TransferJob { total_size: u64, finished_size: u64, transferred: u64, + default_overwrite_strategy: Option, } #[inline] @@ -220,6 +228,7 @@ fn is_compressed_file(name: &str) -> bool { impl TransferJob { pub fn new_write(id: i32, path: String, files: Vec) -> Self { + println!("new write {}", path); let total_size = files.iter().map(|x| x.size as u64).sum(); Self { id, @@ -231,6 +240,7 @@ impl TransferJob { } pub fn new_read(id: i32, path: String, include_hidden: bool) -> ResultType { + println!("new read {}", path); let files = get_recursive_files(&path, include_hidden)?; let total_size = files.iter().map(|x| x.size as u64).sum(); Ok(Self { @@ -342,7 +352,7 @@ impl TransferJob { } #[inline] - fn join(&self, name: &str) -> PathBuf { + pub fn join(&self, name: &str) -> PathBuf { if name.is_empty() { self.path.clone() } else { @@ -413,6 +423,13 @@ impl TransferJob { ..Default::default() })) } + pub fn set_overwrite_strategy(&mut self, overwrite_strategy: Option) { + self.default_overwrite_strategy = overwrite_strategy; + } + + pub fn default_overwrite_strategy(&self) -> Option { + self.default_overwrite_strategy + } } #[inline] @@ -468,6 +485,7 @@ pub fn new_receive(id: i32, path: String, files: Vec) -> Message { #[inline] pub fn new_send(id: i32, path: String, include_hidden: bool) -> Message { + println!("new send: {},id : {}", path, id); let mut action = FileAction::new(); action.set_send(FileTransferSendRequest { id, @@ -558,3 +576,27 @@ pub fn create_dir(dir: &str) -> ResultType<()> { std::fs::create_dir_all(get_path(dir))?; Ok(()) } + +#[inline] +pub fn is_write_need_confirmation( + file_path: &str, + digest: &FileTransferDigest, +) -> ResultType { + let path = Path::new(file_path); + if path.exists() && path.is_file() { + let metadata = std::fs::metadata(path)?; + let modified_time = metadata.modified()?; + let remote_mt = Duration::from_millis(digest.last_edit_timestamp); + let local_mt = modified_time.duration_since(UNIX_EPOCH)?; + // if + // is_recv && remote_mt >= local_mt) || (!is_recv && remote_mt <= local_mt) || + if remote_mt == local_mt && digest.file_size == metadata.len() { + // I'm recving or sending an newer modified file! + // or a + return Ok(false); + } + Ok(true) + } else { + Ok(false) + } +} diff --git a/src/client.rs b/src/client.rs index cfe02c20c..e8d339262 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1323,6 +1323,8 @@ pub enum Data { AddPortForward((i32, String, i32)), ToggleClipboardFile, NewRDP, + // ConfirmOverrideFile((i32, String, String, bool, bool)), + SetConfirmOverrideFile((i32, i32, bool, bool)), } #[derive(Clone)] @@ -1332,6 +1334,12 @@ pub enum Key { _Raw(u32), } +#[derive(Clone)] +pub enum OverrideStrategy { + Skip, + Overwrite, +} + lazy_static::lazy_static! { pub static ref KEY_MAP: HashMap<&'static str, Key> = [ diff --git a/src/ui/file_transfer.tis b/src/ui/file_transfer.tis index d66a8688e..87cb0b17b 100644 --- a/src/ui/file_transfer.tis +++ b/src/ui/file_transfer.tis @@ -679,6 +679,7 @@ function confirmDelete(id ,path, is_remote) { } handler.confirmDeleteFiles = function(id, i, name) { + stdout.println("id=" + id +", i=" +",name="+name); var jt = file_transfer.job_table; var job = jt.job_map[id]; if (!job) return; @@ -716,6 +717,28 @@ handler.confirmDeleteFiles = function(id, i, name) { }); } +handler.overrideFileConfirm = function(id, file_num, to) { + var jt = file_transfer.job_table; + var job = jt.job_map[id]; + stdout.println("job type: " + job.type); + stdout.println(id + path + to); + stdout.println(JSON.stringify(job)); + msgbox("custom-skip", "Confirm Write Strategy", "
\ +
" + translate('Overwrite') + translate('files') + ".
\ +
" + translate('This file exists in your computer, skip or overwrite this file?') + "
\ + " + to + "
\ +
" + translate('Do this for all conflicts') + "
\ + ", function(res=null) { + if (!res) { + jt.updateJobStatus(id, -1, "cancel"); + } else if (res.skip) { + handler.set_write_override(id,file_num,false,true); // + } else { + handler.set_write_override(id,file_num,true,false); // + } + }); +} + function save_file_transfer_close_state() { var local_dir = file_transfer.local_folder_view.fd.path || ""; var local_show_hidden = file_transfer.local_folder_view.show_hidden ? "Y" : ""; diff --git a/src/ui/remote.rs b/src/ui/remote.rs index de9ca032c..e4e3265a9 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -3,6 +3,7 @@ use crate::clipboard_file::*; use crate::{ client::*, common::{self, check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL}, + VERSION, }; #[cfg(windows)] use clipboard::{ @@ -10,10 +11,11 @@ use clipboard::{ get_rx_clip_client, server_clip_file, }; use enigo::{self, Enigo, KeyboardControllable}; +use hbb_common::fs::{get_string, is_file_exists}; use hbb_common::{ allow_err, - config::{Config, LocalConfig, PeerConfig}, - fs, log, + config::{self, Config, LocalConfig, PeerConfig}, + fs, get_version_number, log, message_proto::{permission_info::Permission, *}, protobuf::Message as _, rendezvous_proto::ConnType, @@ -216,6 +218,7 @@ impl sciter::EventHandler for Handler { fn toggle_option(String); fn get_remember(); fn peer_platform(); + fn set_write_override(i32,i32, bool,bool); // , } } @@ -536,6 +539,22 @@ impl Handler { self.lc.read().unwrap().remember } + fn set_write_override( + &mut self, + job_id: i32, + file_num: i32, + is_override: bool, + remember: bool, + ) -> bool { + self.send(Data::SetConfirmOverrideFile(( + job_id, + file_num, + is_override, + remember, + ))); + true + } + fn t(&self, name: String) -> String { crate::client::translate(name) } @@ -1498,6 +1517,7 @@ impl Remote { } async fn handle_msg_from_ui(&mut self, data: Data, peer: &mut Stream) -> bool { + println!("new msg from ui"); match data { Data::Close => { return false; @@ -1514,8 +1534,9 @@ impl Remote { allow_err!(peer.send(&msg).await); } Data::SendFiles((id, path, to, include_hidden, is_remote)) => { + println!("send files, is remote {}", is_remote); if is_remote { - log::debug!("New job {}, write to {} from remote {}", id, to, path); + println!("New job {}, write to {} from remote {}", id, to, path); self.write_jobs .push(fs::TransferJob::new_write(id, to, Vec::new())); allow_err!(peer.send(&fs::new_send(id, path, include_hidden)).await); @@ -1525,7 +1546,7 @@ impl Remote { self.handle_job_status(id, -1, Some(err.to_string())); } Ok(job) => { - log::debug!( + println!( "New job {}, read {} to remote {}, {} files", id, path, @@ -1558,6 +1579,27 @@ impl Remote { } } } + Data::SetConfirmOverrideFile((id, file_num, need_override, remember)) => { + if let Some(job) = fs::get_job(id, &mut self.write_jobs) { + if remember { + job.set_overwrite_strategy(Some(need_override)); + } + let mut msg = Message::new(); + let mut file_action = FileAction::new(); + file_action.set_send_confirm(FileTransferSendConfirmRequest { + id, + file_num, + union: if need_override { + Some(file_transfer_send_confirm_request::Union::offset_blk(0)) + } else { + Some(file_transfer_send_confirm_request::Union::skip(true)) + }, + ..Default::default() + }); + msg.set_file_action(file_action); + allow_err!(peer.send(&msg).await); + } + } Data::RemoveDirAll((id, path, is_remote)) => { let sep = self.handler.get_path_sep(is_remote); if is_remote { @@ -1784,7 +1826,11 @@ impl Remote { } Some(message::Union::file_response(fr)) => match fr.union { Some(file_response::Union::dir(fd)) => { + println!("file_response is dir: {}", fd.path); let entries = fd.entries.to_vec(); + for entry in &entries { + println!("dir file: {}", entry.name); + } let mut m = make_fd(fd.id, &entries, fd.id > 0); if fd.id <= 0 { m.set_item("path", fd.path); @@ -1796,15 +1842,77 @@ impl Remote { job.files = entries; } } + Some(file_response::Union::digest(digest)) => { + if let Some(job) = fs::get_job(digest.id, &mut self.write_jobs) { + if let Some(file) = job.files().get(digest.file_num as usize) { + let write_path = get_string(&job.join(&file.name)); + let overwrite_strategy = job.default_overwrite_strategy(); + match fs::is_write_need_confirmation(&write_path, &digest) { + Ok(res) => { + if res { + // need confirm + if overwrite_strategy.is_none() { + self.handler.call( + "overrideFileConfirm", + &make_args!( + digest.id, + digest.file_num, + write_path + ), + ); + } else { + let mut msg = Message::new(); + let mut file_action = FileAction::new(); + file_action + .set_send_confirm(FileTransferSendConfirmRequest { + id: digest.id, + file_num: digest.file_num, + union: Some( + if overwrite_strategy.unwrap() { + file_transfer_send_confirm_request::Union::offset_blk(0) + } else { + file_transfer_send_confirm_request::Union::skip(true) + }, + ), + ..Default::default() + }); + msg.set_file_action(file_action); + allow_err!(peer.send(&msg).await); + } + } else { + // file with digest need send + let mut msg = Message::new(); + let mut file_action = FileAction::new(); + file_action + .set_send_confirm(FileTransferSendConfirmRequest { + id: digest.id, + file_num: digest.file_num, + union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)), + ..Default::default() + }); + msg.set_file_action(file_action); + allow_err!(peer.send(&msg).await); + } + } + Err(err) => { + println!("error recving digest: {}", err); + } + } + } + } + } Some(file_response::Union::block(block)) => { + println!("file response block, file num: {}", block.file_num); if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) { if let Err(_err) = job.write(block, None).await { // to-do: add "skip" for writing job + println!("error: {}", _err); } self.update_jobs_status(); } } Some(file_response::Union::done(d)) => { + println!("file response done"); if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) { job.modify_time(); fs::remove_job(d.id, &mut self.write_jobs);