fix(file-transfer): improve large directory loading (#15830)

* fix(file-transfer): improve large directory loading

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file-transfer): avoid failing newer directory reads

Track each remote directory request by its registered completer and only remove
the task when it still matches, preventing stale failures from affecting newer
requests for the same path.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file-transfer): handle slow directory listings safely

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file transfer): correlate directory responses with requests

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file transfer): prevent automatic directory responses from matching requests

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file-transfer): handle large remote directory listings reliably

- build file rows lazily
- register remote reads before sending requests
- handle Home paths, stale responses, errors, and timeouts
- serialize same-path reads with different hidden-file options

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file transfer): reduce diffs

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: build

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: invalidate pending dir reads on reconnect

Signed-off-by: fufesou <linlong1266@gmail.com>

* test(file-transfer): cover remote directory read lifecycle

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2026-08-27 13:01:11 +08:00
committed by GitHub
parent e9b81e3475
commit 0b08a83d4b
4 changed files with 440 additions and 45 deletions

View File

@@ -1546,13 +1546,19 @@ async fn read_dir(dir: &str, include_hidden: bool, tx: &UnboundedSender<Data>) {
fs::get_path(dir)
}
};
if let Ok(Ok(fd)) = spawn_blocking(move || fs::read_dir(&path, include_hidden)).await {
let mut msg_out = Message::new();
let mut file_response = FileResponse::new();
file_response.set_dir(fd);
msg_out.set_file_response(file_response);
send_raw(msg_out, tx);
}
let result = spawn_blocking(move || fs::read_dir(&path, include_hidden)).await;
let msg_out = match result {
Ok(Ok(fd)) => {
let mut msg_out = Message::new();
let mut file_response = FileResponse::new();
file_response.set_dir(fd);
msg_out.set_file_response(file_response);
msg_out
}
Ok(Err(err)) => fs::new_error(0, err, -1),
Err(err) => fs::new_error(0, err, -1),
};
send_raw(msg_out, tx);
}
#[cfg(not(any(target_os = "ios")))]
@@ -1750,7 +1756,7 @@ mod tests {
#[test]
#[cfg(not(any(target_os = "ios")))]
fn read_dir_success() {
fn read_dir_reports_success_and_error() {
let rt = Runtime::new().unwrap();
rt.block_on(async {
let (tx, mut rx) = unbounded_channel();
@@ -1773,6 +1779,18 @@ mod tests {
_ => panic!("unexpected data"),
}
let _ = fs::remove_dir_all(&dir);
super::read_dir(&dir.to_string_lossy(), false, &tx).await;
match rx.recv().await.unwrap() {
Data::RawMessage(bytes) => {
let mut msg = Message::new();
msg.merge_from_bytes(&bytes).unwrap();
assert_eq!(msg.file_response().error().id, 0);
assert!(!msg.file_response().error().error.is_empty());
}
_ => panic!("unexpected data"),
}
});
}