fix: file transfer, auto start on reconnect (#13329)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2025-10-29 15:15:05 +08:00
committed by GitHub
parent 265d08fc3b
commit e3fcc6cce3
6 changed files with 119 additions and 30 deletions

View File

@@ -92,6 +92,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
gFFI.dialogManager.dismissAll(); gFFI.dialogManager.dismissAll();
WakelockPlus.disable(); WakelockPlus.disable();
}); });
model.jobController.clear();
super.dispose(); super.dispose();
} }

View File

@@ -1033,30 +1033,54 @@ class JobController {
await bind.sessionCancelJob(sessionId: sessionId, actId: id); await bind.sessionCancelJob(sessionId: sessionId, actId: id);
} }
void loadLastJob(Map<String, dynamic> evt) { Future<void> loadLastJob(Map<String, dynamic> evt) async {
debugPrint("load last job: $evt"); debugPrint("load last job: $evt");
Map<String, dynamic> jobDetail = json.decode(evt['value']); Map<String, dynamic> jobDetail = json.decode(evt['value']);
// int id = int.parse(jobDetail['id']);
String remote = jobDetail['remote']; String remote = jobDetail['remote'];
String to = jobDetail['to']; String to = jobDetail['to'];
bool showHidden = jobDetail['show_hidden']; bool showHidden = jobDetail['show_hidden'];
int fileNum = jobDetail['file_num']; int fileNum = jobDetail['file_num'];
bool isRemote = jobDetail['is_remote']; bool isRemote = jobDetail['is_remote'];
final currJobId = JobController.jobID.next(); bool isAutoStart = jobDetail['auto_start'] == true;
String fileName = path.basename(isRemote ? remote : to); int currJobId = -1;
var jobProgress = JobProgress() if (isAutoStart) {
..type = JobType.transfer // Ensure jobDetail['id'] exists and is an int
..fileName = fileName if (jobDetail.containsKey('id') &&
..jobName = isRemote ? remote : to jobDetail['id'] != null &&
..id = currJobId jobDetail['id'] is int) {
..isRemoteToLocal = isRemote currJobId = jobDetail['id'];
..fileNum = fileNum }
..remote = remote }
..to = to if (currJobId < 0) {
..showHidden = showHidden // If id is missing or invalid, disable auto-start and assign a new job id
..state = JobState.paused; isAutoStart = false;
jobTable.add(jobProgress); currJobId = JobController.jobID.next();
bind.sessionAddJob( }
if (!isAutoStart) {
if (!(isDesktop || isWebDesktop)) {
// Don't add to job table if not auto start on mobile.
// Because mobile does not support job list view now.
return;
}
// Add to job table if not auto start on desktop.
String fileName = path.basename(isRemote ? remote : to);
final jobProgress = JobProgress()
..type = JobType.transfer
..fileName = fileName
..jobName = isRemote ? remote : to
..id = currJobId
..isRemoteToLocal = isRemote
..fileNum = fileNum
..remote = remote
..to = to
..showHidden = showHidden
..state = JobState.paused;
jobTable.add(jobProgress);
}
await bind.sessionAddJob(
sessionId: sessionId, sessionId: sessionId,
isRemote: isRemote, isRemote: isRemote,
includeHidden: showHidden, includeHidden: showHidden,
@@ -1065,6 +1089,11 @@ class JobController {
to: isRemote ? to : remote, to: isRemote ? to : remote,
fileNum: fileNum, fileNum: fileNum,
); );
if (isAutoStart) {
await bind.sessionResumeJob(
sessionId: sessionId, actId: currJobId, isRemote: isRemote);
}
} }
void resumeJob(int jobId) { void resumeJob(int jobId) {
@@ -1095,6 +1124,11 @@ class JobController {
} }
debugPrint("update folder files: $info"); debugPrint("update folder files: $info");
} }
void clear() {
jobTable.clear();
jobResultListener.clear();
}
} }
class JobResultListener<T> { class JobResultListener<T> {

View File

@@ -23,7 +23,7 @@ use std::{
os::raw::{c_char, c_int, c_void}, os::raw::{c_char, c_int, c_void},
str::FromStr, str::FromStr,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, RwLock, Arc, RwLock,
}, },
}; };
@@ -756,7 +756,7 @@ impl InvokeUiSession for FlutterHandler {
// unused in flutter // unused in flutter
fn clear_all_jobs(&self) {} fn clear_all_jobs(&self) {}
fn load_last_job(&self, _cnt: i32, job_json: &str) { fn load_last_job(&self, _cnt: i32, job_json: &str, _auto_start: bool) {
self.push_event("load_last_job", &[("value", job_json)], &[]); self.push_event("load_last_job", &[("value", job_json)], &[]);
} }
@@ -1328,6 +1328,7 @@ pub fn session_add(
server_keyboard_enabled: Arc::new(RwLock::new(true)), server_keyboard_enabled: Arc::new(RwLock::new(true)),
server_file_transfer_enabled: Arc::new(RwLock::new(true)), server_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)), server_clipboard_enabled: Arc::new(RwLock::new(true)),
reconnect_count: Arc::new(AtomicUsize::new(0)),
..Default::default() ..Default::default()
}; };

View File

@@ -137,7 +137,7 @@ class JobTable: Reactor.Component {
self.timer(30ms, function() { self.update(); }); self.timer(30ms, function() { self.update(); });
} }
function addJob(id, path, to, file_num, show_hidden, is_remote) { function addJob(id, path, to, file_num, show_hidden, is_remote, auto_start) {
var job = { type: "transfer", var job = { type: "transfer",
id: id, path: path, to: to, id: id, path: path, to: to,
include_hidden: show_hidden, include_hidden: show_hidden,
@@ -146,6 +146,10 @@ class JobTable: Reactor.Component {
this.job_map[id] = this.jobs[this.jobs.length - 1]; this.job_map[id] = this.jobs[this.jobs.length - 1];
handler.update_next_job_id(id + 1); handler.update_next_job_id(id + 1);
handler.add_job(id, 0, path, to, file_num, show_hidden, is_remote); handler.add_job(id, 0, path, to, file_num, show_hidden, is_remote);
if (auto_start) {
this.continueJob(id);
this.update();
}
stdout.println(JSON.stringify(job)); stdout.println(JSON.stringify(job));
} }
@@ -279,7 +283,8 @@ class JobTable: Reactor.Component {
if (!err) { if (!err) {
handler.remove_dir(job.id, job.path, job.is_remote); handler.remove_dir(job.id, job.path, job.is_remote);
refreshDir(job.is_remote); refreshDir(job.is_remote);
if (is_remote) file_transfer.remote_folder_view.table.resetCurrent(); // Use the job's is_remote; local variable `is_remote` is undefined in this scope.
if (job.is_remote) file_transfer.remote_folder_view.table.resetCurrent();
else file_transfer.local_folder_view.table.resetCurrent(); else file_transfer.local_folder_view.table.resetCurrent();
} }
} else if (!job.no_confirm) { } else if (!job.no_confirm) {
@@ -697,9 +702,9 @@ handler.clearAllJobs = function() {
file_transfer.job_table.clearAllJobs(); file_transfer.job_table.clearAllJobs();
} }
handler.addJob = function (id, path, to, file_num, show_hidden, is_remote) { // load last job handler.addJob = function (id, path, to, file_num, show_hidden, is_remote, auto_start) { // load last job
// stdout.println("restore job: " + is_remote); // stdout.println("restore job: " + is_remote);
file_transfer.job_table.addJob(id,path,to,file_num,show_hidden,is_remote); file_transfer.job_table.addJob(id,path,to,file_num,show_hidden,is_remote,auto_start);
} }
handler.updateTransferList = function () { handler.updateTransferList = function () {

View File

@@ -1,7 +1,7 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
sync::{Arc, Mutex, RwLock}, sync::{atomic::AtomicUsize, Arc, Mutex, RwLock},
}; };
use sciter::{ use sciter::{
@@ -199,7 +199,7 @@ impl InvokeUiSession for SciterHandler {
self.call("clearAllJobs", &make_args!()); self.call("clearAllJobs", &make_args!());
} }
fn load_last_job(&self, cnt: i32, job_json: &str) { fn load_last_job(&self, cnt: i32, job_json: &str, auto_start: bool) {
let job: Result<TransferJobMeta, serde_json::Error> = serde_json::from_str(job_json); let job: Result<TransferJobMeta, serde_json::Error> = serde_json::from_str(job_json);
if let Ok(job) = job { if let Ok(job) = job {
let path; let path;
@@ -213,7 +213,15 @@ impl InvokeUiSession for SciterHandler {
} }
self.call( self.call(
"addJob", "addJob",
&make_args!(cnt, path, to, job.file_num, job.show_hidden, job.is_remote), &make_args!(
cnt,
path,
to,
job.file_num,
job.show_hidden,
job.is_remote,
auto_start
),
); );
} }
} }
@@ -570,6 +578,7 @@ impl SciterSession {
server_keyboard_enabled: Arc::new(RwLock::new(true)), server_keyboard_enabled: Arc::new(RwLock::new(true)),
server_file_transfer_enabled: Arc::new(RwLock::new(true)), server_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)), server_clipboard_enabled: Arc::new(RwLock::new(true)),
reconnect_count: Arc::new(AtomicUsize::new(0)),
..Default::default() ..Default::default()
}; };

View File

@@ -29,7 +29,10 @@ use std::{
collections::HashMap, collections::HashMap,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
str::FromStr, str::FromStr,
sync::{Arc, Mutex, RwLock}, sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex, RwLock,
},
time::SystemTime, time::SystemTime,
}; };
use uuid::Uuid; use uuid::Uuid;
@@ -61,6 +64,9 @@ pub struct Session<T: InvokeUiSession> {
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>, pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
pub connection_round_state: Arc<Mutex<ConnectionRoundState>>, pub connection_round_state: Arc<Mutex<ConnectionRoundState>>,
pub printer_names: Arc<RwLock<HashMap<i32, String>>>, pub printer_names: Arc<RwLock<HashMap<i32, String>>>,
// Indicate whether the session is reconnected.
// Used to auto start file transfer after reconnection.
pub reconnect_count: Arc<AtomicUsize>,
} }
#[derive(Clone)] #[derive(Clone)]
@@ -1272,6 +1278,7 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.write().unwrap().force_relay = true; self.lc.write().unwrap().force_relay = true;
} }
self.lc.write().unwrap().peer_info = None; self.lc.write().unwrap().peer_info = None;
self.reconnect_count.fetch_add(1, Ordering::SeqCst);
let mut lock = self.thread.lock().unwrap(); let mut lock = self.thread.lock().unwrap();
// No need to join the previous thread, because it will exit automatically. // No need to join the previous thread, because it will exit automatically.
// And the previous thread will not change important states. // And the previous thread will not change important states.
@@ -1372,6 +1379,24 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Close); self.send(Data::Close);
} }
fn try_auto_start_job_str(is_reconnected: bool, job_str: &str) -> Option<String> {
if is_reconnected {
let job_str = job_str.trim();
if let Some(stripped) = job_str.strip_suffix('}') {
format!(r#"{},"auto_start": true}}"#, stripped).into()
} else {
// unreachable in normal cases
log::warn!(
"The last character is not '}}': {}, auto start is ignored on flutter",
job_str
);
Some(job_str.to_owned())
}
} else {
None
}
}
pub fn load_last_jobs(&self) { pub fn load_last_jobs(&self) {
self.clear_all_jobs(); self.clear_all_jobs();
let pc = self.load_config(); let pc = self.load_config();
@@ -1379,18 +1404,32 @@ impl<T: InvokeUiSession> Session<T> {
// no last jobs // no last jobs
return; return;
} }
let reconnect_count_thr = if cfg!(feature = "flutter") { 0 } else { 1 };
let is_reconnected = self.reconnect_count.load(Ordering::SeqCst) > reconnect_count_thr;
// TODO: can add a confirm dialog // TODO: can add a confirm dialog
let mut cnt = 1; let mut cnt = 1;
for job_str in pc.transfer.read_jobs.iter() { for job_str in pc.transfer.read_jobs.iter() {
if !job_str.is_empty() { if !job_str.is_empty() {
self.load_last_job(cnt, job_str); self.load_last_job(
cnt,
Self::try_auto_start_job_str(is_reconnected, job_str)
.as_deref()
.unwrap_or(job_str),
is_reconnected,
);
cnt += 1; cnt += 1;
log::info!("restore read_job: {:?}", job_str); log::info!("restore read_job: {:?}", job_str);
} }
} }
for job_str in pc.transfer.write_jobs.iter() { for job_str in pc.transfer.write_jobs.iter() {
if !job_str.is_empty() { if !job_str.is_empty() {
self.load_last_job(cnt, job_str); self.load_last_job(
cnt,
Self::try_auto_start_job_str(is_reconnected, job_str)
.as_deref()
.unwrap_or(job_str),
is_reconnected,
);
cnt += 1; cnt += 1;
log::info!("restore write_job: {:?}", job_str); log::info!("restore write_job: {:?}", job_str);
} }
@@ -1623,7 +1662,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn clear_all_jobs(&self); fn clear_all_jobs(&self);
fn new_message(&self, msg: String); fn new_message(&self, msg: String);
fn update_transfer_list(&self); fn update_transfer_list(&self);
fn load_last_job(&self, cnt: i32, job_json: &str); fn load_last_job(&self, cnt: i32, job_json: &str, auto_start: bool);
fn update_folder_files( fn update_folder_files(
&self, &self,
id: i32, id: i32,