Skip to content

Commit a66adf6

Browse files
committed
MTP: Instant cancel via USB SIC abort
- Bump `mtp-rs` from 0.9.1 to 0.11.0 for `FileDownload::cancel()` support - On cancel, call `download.cancel(DEFAULT_CANCEL_TIMEOUT)` to cleanly abort the USB transfer (~300ms) instead of draining the entire stream - Fixes regression where breaking mid-stream corrupted the USB session, making the device unresponsive until reconnect
1 parent 00c5f18 commit a66adf6

3 files changed

Lines changed: 40 additions & 39 deletions

File tree

Cargo.lock

Lines changed: 17 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop/src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ mime_guess = "2"
109109

110110
[target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies]
111111
# MTP (Android device) support via pure Rust implementation
112-
mtp-rs = "0.9.1"
112+
mtp-rs = "0.11.0"
113113
# USB hotplug detection for MTP device watcher
114114
nusb = "0.2.3"
115115
bytes = "1"

apps/desktop/src-tauri/src/mtp/connection/file_ops.rs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,8 @@ impl MtpConnectionManager {
137137
message: format!("Failed to create local file: {}", e),
138138
})?;
139139

140-
// Write chunks to file (must complete before releasing device lock).
141-
// MTP USB transfers must be fully consumed — breaking mid-stream corrupts
142-
// the session and makes the device unresponsive for subsequent operations.
143-
// On cancel: stop writing to disk, but keep draining the USB stream.
140+
// Write chunks to file, checking for cancellation between chunks.
141+
// On cancel: use mtp-rs's USB SIC cancel to abort the transfer cleanly (~300ms).
144142
let mut bytes_written = 0u64;
145143
let mut cancelled = false;
146144
while let Some(chunk_result) = download.next_chunk().await {
@@ -149,26 +147,29 @@ impl MtpConnectionManager {
149147
message: format!("Download error: {}", e),
150148
})?;
151149

152-
if !cancelled {
153-
file.write_all(&chunk).await.map_err(|e| MtpConnectionError::Other {
154-
device_id: device_id.to_string(),
155-
message: format!("Failed to write local file: {}", e),
156-
})?;
157-
158-
bytes_written += chunk.len() as u64;
159-
160-
// Report progress and check for cancellation
161-
if let Some(ref cb) = on_progress
162-
&& cb(bytes_written, total_size).is_break()
163-
{
164-
cancelled = true;
165-
// Don't break — keep draining the USB stream to keep the session healthy
166-
}
150+
file.write_all(&chunk).await.map_err(|e| MtpConnectionError::Other {
151+
device_id: device_id.to_string(),
152+
message: format!("Failed to write local file: {}", e),
153+
})?;
154+
155+
bytes_written += chunk.len() as u64;
156+
157+
// Report progress and check for cancellation
158+
if let Some(ref cb) = on_progress
159+
&& cb(bytes_written, total_size).is_break()
160+
{
161+
cancelled = true;
162+
break;
167163
}
168-
// else: cancelled, just drain remaining chunks without writing
169164
}
170165

171-
// Release device lock after download completes
166+
// On cancellation, abort the USB transfer cleanly before releasing the lock
167+
if cancelled {
168+
let _ = download.cancel(mtp_rs::DEFAULT_CANCEL_TIMEOUT).await;
169+
}
170+
171+
// Release device lock after download completes (or is cancelled)
172+
drop(download);
172173
drop(storage);
173174
drop(device);
174175

0 commit comments

Comments
 (0)