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();
WakelockPlus.disable();
});
model.jobController.clear();
super.dispose();
}

View File

@@ -1033,30 +1033,54 @@ class JobController {
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");
Map<String, dynamic> jobDetail = json.decode(evt['value']);
// int id = int.parse(jobDetail['id']);
String remote = jobDetail['remote'];
String to = jobDetail['to'];
bool showHidden = jobDetail['show_hidden'];
int fileNum = jobDetail['file_num'];
bool isRemote = jobDetail['is_remote'];
final currJobId = JobController.jobID.next();
String fileName = path.basename(isRemote ? remote : to);
var 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);
bind.sessionAddJob(
bool isAutoStart = jobDetail['auto_start'] == true;
int currJobId = -1;
if (isAutoStart) {
// Ensure jobDetail['id'] exists and is an int
if (jobDetail.containsKey('id') &&
jobDetail['id'] != null &&
jobDetail['id'] is int) {
currJobId = jobDetail['id'];
}
}
if (currJobId < 0) {
// If id is missing or invalid, disable auto-start and assign a new job id
isAutoStart = false;
currJobId = JobController.jobID.next();
}
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,
isRemote: isRemote,
includeHidden: showHidden,
@@ -1065,6 +1089,11 @@ class JobController {
to: isRemote ? to : remote,
fileNum: fileNum,
);
if (isAutoStart) {
await bind.sessionResumeJob(
sessionId: sessionId, actId: currJobId, isRemote: isRemote);
}
}
void resumeJob(int jobId) {
@@ -1095,6 +1124,11 @@ class JobController {
}
debugPrint("update folder files: $info");
}
void clear() {
jobTable.clear();
jobResultListener.clear();
}
}
class JobResultListener<T> {

View File

@@ -23,7 +23,7 @@ use std::{
os::raw::{c_char, c_int, c_void},
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering},
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, RwLock,
},
};
@@ -756,7 +756,7 @@ impl InvokeUiSession for FlutterHandler {
// unused in flutter
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)], &[]);
}
@@ -1328,6 +1328,7 @@ pub fn session_add(
server_keyboard_enabled: Arc::new(RwLock::new(true)),
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)),
reconnect_count: Arc::new(AtomicUsize::new(0)),
..Default::default()
};

View File

@@ -137,7 +137,7 @@ class JobTable: Reactor.Component {
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",
id: id, path: path, to: to,
include_hidden: show_hidden,
@@ -146,6 +146,10 @@ class JobTable: Reactor.Component {
this.job_map[id] = this.jobs[this.jobs.length - 1];
handler.update_next_job_id(id + 1);
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));
}
@@ -279,7 +283,8 @@ class JobTable: Reactor.Component {
if (!err) {
handler.remove_dir(job.id, job.path, 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 if (!job.no_confirm) {
@@ -697,9 +702,9 @@ handler.clearAllJobs = function() {
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);
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 () {

View File

@@ -1,7 +1,7 @@
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
sync::{Arc, Mutex, RwLock},
sync::{atomic::AtomicUsize, Arc, Mutex, RwLock},
};
use sciter::{
@@ -199,7 +199,7 @@ impl InvokeUiSession for SciterHandler {
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);
if let Ok(job) = job {
let path;
@@ -213,7 +213,15 @@ impl InvokeUiSession for SciterHandler {
}
self.call(
"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_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)),
reconnect_count: Arc::new(AtomicUsize::new(0)),
..Default::default()
};

View File

@@ -29,7 +29,10 @@ use std::{
collections::HashMap,
ops::{Deref, DerefMut},
str::FromStr,
sync::{Arc, Mutex, RwLock},
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex, RwLock,
},
time::SystemTime,
};
use uuid::Uuid;
@@ -61,6 +64,9 @@ pub struct Session<T: InvokeUiSession> {
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
pub connection_round_state: Arc<Mutex<ConnectionRoundState>>,
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)]
@@ -1272,6 +1278,7 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.write().unwrap().force_relay = true;
}
self.lc.write().unwrap().peer_info = None;
self.reconnect_count.fetch_add(1, Ordering::SeqCst);
let mut lock = self.thread.lock().unwrap();
// No need to join the previous thread, because it will exit automatically.
// And the previous thread will not change important states.
@@ -1372,6 +1379,24 @@ impl<T: InvokeUiSession> Session<T> {
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) {
self.clear_all_jobs();
let pc = self.load_config();
@@ -1379,18 +1404,32 @@ impl<T: InvokeUiSession> Session<T> {
// no last jobs
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
let mut cnt = 1;
for job_str in pc.transfer.read_jobs.iter() {
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;
log::info!("restore read_job: {:?}", job_str);
}
}
for job_str in pc.transfer.write_jobs.iter() {
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;
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 new_message(&self, msg: String);
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(
&self,
id: i32,