diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 0b3140c0d..fc31db549 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -144,6 +144,28 @@ class FileModel extends ChangeNotifier { notifyListeners(); } + overrideFileConfirm(Map evt) async { + final resp = await showFileConfirmDialog( + translate("Overwrite"), "${evt['read_path']}", true); + 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(); + if (resp == null) { + // skip + msg['need_override'] = 'false'; + } else { + // overwrite + msg['need_override'] = 'true'; + } + FFI.setByName("set_confirm_override_file", jsonEncode(msg)); + } + } + jobReset() { _jobProgress.clear(); notifyListeners(); @@ -392,6 +414,60 @@ class FileModel extends ChangeNotifier { useAnimation: false); } + bool fileConfirmCheckboxRemember = false; + + Future showFileConfirmDialog( + String title, String content, bool showCheckbox) async { + return await DialogManager.show( + (setState, Function(bool? v) close) => CustomAlertDialog( + title: Row( + children: [ + Icon(Icons.warning, color: Colors.red), + SizedBox(width: 20), + Text(title) + ], + ), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(translate("This file exists, skip or overwrite this file?"), + style: TextStyle(fontWeight: FontWeight.bold)), + SizedBox(height: 5), + Text(content), + showCheckbox + ? CheckboxListTile( + contentPadding: const EdgeInsets.all(0), + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + translate("Do this for all conflicts"), + ), + value: fileConfirmCheckboxRemember, + onChanged: (v) { + if (v == null) return; + setState(() => fileConfirmCheckboxRemember = v); + }, + ) + : SizedBox.shrink() + ]), + actions: [ + TextButton( + style: flatButtonStyle, + onPressed: () => close(false), + child: Text(translate("Cancel"))), + TextButton( + style: flatButtonStyle, + onPressed: () => close(null), + child: Text(translate("Skip"))), + TextButton( + style: flatButtonStyle, + onPressed: () => close(true), + child: Text(translate("OK"))), + ]), + useAnimation: false); + } + sendRemoveFile(String path, int fileNum, bool isLocal) { final msg = { "id": _jobId.toString(), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index c0e21fc45..6d51f57e2 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -156,6 +156,8 @@ class FfiModel with ChangeNotifier { FFI.fileModel.jobDone(evt); } else if (name == 'job_error') { FFI.fileModel.jobError(evt); + } else if (name == 'override_file_confirm') { + FFI.fileModel.overrideFileConfirm(evt); } else if (name == 'try_start_without_auth') { FFI.serverModel.loginRequest(evt); } else if (name == 'on_client_authorized') { diff --git a/libs/hbb_common/src/fs.rs b/libs/hbb_common/src/fs.rs index 4ba132f57..780cdbbb7 100644 --- a/libs/hbb_common/src/fs.rs +++ b/libs/hbb_common/src/fs.rs @@ -544,6 +544,7 @@ impl TransferJob { } pub fn set_file_confirmed(&mut self, file_confirmed: bool) { + log::info!("id: {}, file_confirmed: {}", self.id, file_confirmed); self.file_confirmed = file_confirmed; } @@ -581,7 +582,6 @@ impl TransferJob { } } Some(file_transfer_send_confirm_request::Union::offset_blk(offset)) => { - log::debug!("file confirmed"); self.set_file_confirmed(true); } _ => {} diff --git a/src/mobile.rs b/src/mobile.rs index cd0942e32..06a93aa1a 100644 --- a/src/mobile.rs +++ b/src/mobile.rs @@ -5,7 +5,7 @@ use hbb_common::{ compress::decompress, config::{Config, LocalConfig}, fs, log, - fs::can_enable_overwrite_detection, + fs::{can_enable_overwrite_detection, new_send_confirm, DigestCheckResult, get_string}, message_proto::*, protobuf::Message as _, rendezvous_proto::ConnType, @@ -200,6 +200,15 @@ impl Session { } } + pub fn set_confirm_override_file(id: i32, file_num: i32, need_override: bool, remember: bool, is_upload: bool) { + if let Some(session) = SESSION.read().unwrap().as_ref() { + if let Some(sender) = session.sender.read().unwrap().as_ref() { + log::info!("confirm file transfer, job: {}, need_override: {}", id, need_override); + sender.send(Data::SetConfirmOverrideFile((id, file_num, need_override, remember, is_upload))).ok(); + } + } + } + #[inline] pub fn send_msg_static(msg: Message) { if let Some(session) = SESSION.read().unwrap().as_ref() { @@ -662,6 +671,88 @@ impl Connection { Some(file_response::Union::error(e)) => { self.handle_job_status(e.id, e.file_num, Some(e.error)); } + Some(file_response::Union::digest(digest)) => { + if digest.is_upload { + if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) { + if let Some(file) = job.files().get(digest.file_num as usize) { + let read_path = get_string(&job.join(&file.name)); + let overwrite_strategy = job.default_overwrite_strategy(); + if let Some(overwrite) = overwrite_strategy { + let req = FileTransferSendConfirmRequest { + id: digest.id, + file_num: digest.file_num, + union: Some(if overwrite { + file_transfer_send_confirm_request::Union::offset_blk(0) + } else { + file_transfer_send_confirm_request::Union::skip( + true, + ) + }), + ..Default::default() + }; + job.confirm(&req); + let msg = new_send_confirm(req); + allow_err!(peer.send(&msg).await); + } else { + self.handle_override_file_confirm(digest.id, digest.file_num, read_path, true); + } + } + } + } else { + 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) => match res { + DigestCheckResult::IsSame => { + let msg= new_send_confirm(FileTransferSendConfirmRequest { + id: digest.id, + file_num: digest.file_num, + union: Some(file_transfer_send_confirm_request::Union::skip(true)), + ..Default::default() + }); + self.session.send_msg(msg); + } + DigestCheckResult::NeedConfirm(digest) => { + if let Some(overwrite) = overwrite_strategy { + let msg = new_send_confirm( + FileTransferSendConfirmRequest { + id: digest.id, + file_num: digest.file_num, + union: Some(if overwrite { + file_transfer_send_confirm_request::Union::offset_blk(0) + } else { + file_transfer_send_confirm_request::Union::skip(true) + }), + ..Default::default() + }, + ); + self.session.send_msg(msg); + } else { + self.handle_override_file_confirm(digest.id, digest.file_num, write_path.to_string(), false); + } + } + DigestCheckResult::NoSuchFile => { + let msg = new_send_confirm( + FileTransferSendConfirmRequest { + id: digest.id, + file_num: digest.file_num, + union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)), + ..Default::default() + }, + ); + self.session.send_msg(msg); + } + }, + Err(err) => { + println!("error recving digest: {}", err); + } + } + } + } + } + } _ => {} }, Some(message::Union::misc(misc)) => match misc.union { @@ -715,6 +806,14 @@ impl Connection { self.audio_handler.handle_frame(frame); } } + Some(message::Union::file_action(action)) => match action.union { + Some(file_action::Union::send_confirm(c)) => { + if let Some(job) = fs::get_job(c.id, &mut self.read_jobs) { + job.confirm(&c); + } + } + _ => {} + }, _ => {} } } @@ -878,6 +977,45 @@ impl Connection { } } } + Data::SetConfirmOverrideFile((id, file_num, need_override, remember, is_upload)) => { + if is_upload { + if let Some(job) = fs::get_job(id, &mut self.read_jobs) { + if remember { + job.set_overwrite_strategy(Some(need_override)); + } + job.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() + }); + } + } else { + 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); + self.session.send_msg(msg); + } + } + } _ => {} } true @@ -949,6 +1087,13 @@ impl Connection { ); } } + + fn handle_override_file_confirm(&mut self, id: i32, file_num: i32, read_path: String, is_upload: bool) { + self.session.push_event( + "override_file_confirm", + vec![("id", &id.to_string()), ("file_num", &file_num.to_string()), ("read_path", &read_path), ("is_upload", &is_upload.to_string())] + ); + } } pub fn make_fd_to_json(fd: FileDirectory) -> String { @@ -994,7 +1139,7 @@ pub mod connection_manager { self, sync::mpsc::{UnboundedReceiver, UnboundedSender}, task::spawn_blocking, - }, + }, fs::is_write_need_confirmation, }; use scrap::android::call_main_service_set_by_name; use serde_derive::Serialize; @@ -1259,6 +1404,59 @@ pub mod connection_manager { } } } + ipc::FS::CheckDigest { + id, + file_num, + file_size, + last_modified, + is_upload, + } => { + if let Some(job) = fs::get_job(id, &mut *WRITE_JOBS.lock().unwrap()) { + let mut req = FileTransferSendConfirmRequest { + id, + file_num, + union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)), + ..Default::default() + }; + let digest = FileTransferDigest { + id, + file_num, + last_modified, + file_size, + ..Default::default() + }; + if let Some(file) = job.files().get(file_num as usize) { + let path = get_string(&job.join(&file.name)); + match is_write_need_confirmation(&path, &digest) { + Ok(digest_result) => { + match digest_result { + DigestCheckResult::IsSame => { + req.set_skip(true); + let msg_out = new_send_confirm(req); + send_raw(msg_out, &tx); + } + DigestCheckResult::NeedConfirm(mut digest) => { + // upload to server, but server has the same file, request + digest.is_upload = is_upload; + let mut msg_out = Message::new(); + let mut fr = FileResponse::new(); + fr.set_digest(digest); + msg_out.set_file_response(fr); + send_raw(msg_out, &tx); + } + DigestCheckResult::NoSuchFile => { + let msg_out = new_send_confirm(req); + send_raw(msg_out, &tx); + } + } + } + Err(err) => { + send_raw(fs::new_error(id, err, file_num), &tx); + } + } + } + } + } _ => {} } } diff --git a/src/mobile_ffi.rs b/src/mobile_ffi.rs index e26d8c7ae..8fadf1cce 100644 --- a/src/mobile_ffi.rs +++ b/src/mobile_ffi.rs @@ -346,6 +346,31 @@ unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) { } } } + "set_confirm_override_file" => { + if let Ok(m) = serde_json::from_str::>(value) { + if let ( + Some(id), + Some(file_num), + Some(need_override), + Some(remember), + Some(is_upload), + ) = ( + m.get("id"), + m.get("file_num"), + m.get("need_override"), + m.get("remember"), + m.get("is_upload") + ) { + Session::set_confirm_override_file( + id.parse().unwrap_or(0), + file_num.parse().unwrap_or(0), + need_override.eq("true"), + remember.eq("true"), + is_upload.eq("true"), + ); + } + } + } "remove_file" => { if let Ok(m) = serde_json::from_str::>(value) { if let (