Skip to content

Commit 707a96a

Browse files
committed
Refactor: Split connection.rs
1 parent c2f9df0 commit 707a96a

10 files changed

Lines changed: 3622 additions & 3526 deletions

File tree

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

Lines changed: 0 additions & 3520 deletions
This file was deleted.
Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
//! Bulk and recursive MTP operations (scan, recursive download/upload).
2+
3+
use log::debug;
4+
use std::path::Path;
5+
6+
use super::errors::MtpConnectionError;
7+
use super::{MtpConnectionManager, normalize_mtp_path};
8+
use crate::file_system::CopyScanResult;
9+
10+
impl MtpConnectionManager {
11+
/// Scans an MTP path recursively to get statistics for a copy operation.
12+
///
13+
/// # Arguments
14+
///
15+
/// * `device_id` - The connected device ID
16+
/// * `storage_id` - The storage ID within the device
17+
/// * `path` - Virtual path on the device to scan
18+
///
19+
/// # Returns
20+
///
21+
/// Statistics including file count, directory count, and total bytes.
22+
pub async fn scan_for_copy(
23+
&self,
24+
device_id: &str,
25+
storage_id: u32,
26+
path: &str,
27+
) -> Result<CopyScanResult, MtpConnectionError> {
28+
debug!(
29+
"MTP scan_for_copy: device={}, storage={}, path={}",
30+
device_id, storage_id, path
31+
);
32+
33+
// Try to list the directory - if it fails or returns empty, it might be a file
34+
let entries = match self.list_directory(device_id, storage_id, path).await {
35+
Ok(entries) => entries,
36+
Err(e) => {
37+
// list_directory failed - this might be because path is a file, not a directory.
38+
// Try to check by listing the parent directory.
39+
debug!(
40+
"MTP scan_for_copy: list_directory failed for '{}', checking if it's a file: {:?}",
41+
path, e
42+
);
43+
if let Some(result) = self.try_scan_as_file(device_id, storage_id, path).await {
44+
return Ok(result);
45+
}
46+
// Not a file either, propagate the original error
47+
return Err(e);
48+
}
49+
};
50+
51+
let mut file_count = 0usize;
52+
let mut dir_count = 0usize;
53+
let mut total_bytes = 0u64;
54+
55+
// If entries is empty, it might be an empty directory OR a file (some MTP devices
56+
// return empty for files instead of an error)
57+
if entries.is_empty() {
58+
if let Some(result) = self.try_scan_as_file(device_id, storage_id, path).await {
59+
return Ok(result);
60+
}
61+
// Empty directory
62+
return Ok(CopyScanResult {
63+
file_count: 0,
64+
dir_count: 1,
65+
total_bytes: 0,
66+
});
67+
}
68+
69+
// Process entries recursively
70+
for entry in &entries {
71+
if entry.is_directory {
72+
dir_count += 1;
73+
// Recursively scan subdirectory
74+
let child_result = Box::pin(self.scan_for_copy(device_id, storage_id, &entry.path)).await?;
75+
file_count += child_result.file_count;
76+
dir_count += child_result.dir_count;
77+
total_bytes += child_result.total_bytes;
78+
} else {
79+
file_count += 1;
80+
total_bytes += entry.size.unwrap_or(0);
81+
}
82+
}
83+
84+
debug!(
85+
"MTP scan_for_copy: {} files, {} dirs, {} bytes for {}",
86+
file_count, dir_count, total_bytes, path
87+
);
88+
89+
Ok(CopyScanResult {
90+
file_count,
91+
dir_count,
92+
total_bytes,
93+
})
94+
}
95+
96+
/// Helper to check if a path is a file by listing its parent directory.
97+
/// Returns Some(CopyScanResult) if path is a file, None otherwise.
98+
async fn try_scan_as_file(&self, device_id: &str, storage_id: u32, path: &str) -> Option<CopyScanResult> {
99+
let path_buf = normalize_mtp_path(path);
100+
let parent = path_buf.parent()?;
101+
let name = path_buf.file_name()?.to_str()?;
102+
103+
let parent_entries = self
104+
.list_directory(device_id, storage_id, &parent.to_string_lossy())
105+
.await
106+
.ok()?;
107+
108+
let entry = parent_entries.iter().find(|e| e.name == name)?;
109+
110+
if entry.is_directory {
111+
// It's a directory, not a file - let caller handle it
112+
return None;
113+
}
114+
115+
debug!(
116+
"MTP scan_for_copy: path '{}' is a file with size {}",
117+
path,
118+
entry.size.unwrap_or(0)
119+
);
120+
121+
Some(CopyScanResult {
122+
file_count: 1,
123+
dir_count: 0,
124+
total_bytes: entry.size.unwrap_or(0),
125+
})
126+
}
127+
128+
/// Downloads a file or directory recursively from the MTP device to a local path.
129+
///
130+
/// # Arguments
131+
///
132+
/// * `device_id` - The connected device ID
133+
/// * `storage_id` - The storage ID within the device
134+
/// * `object_path` - Virtual path on the device to download
135+
/// * `local_dest` - Local destination path
136+
///
137+
/// # Returns
138+
///
139+
/// Total bytes transferred.
140+
pub async fn download_recursive(
141+
&self,
142+
device_id: &str,
143+
storage_id: u32,
144+
object_path: &str,
145+
local_dest: &Path,
146+
) -> Result<u64, MtpConnectionError> {
147+
debug!(
148+
"MTP download_recursive: device={}, storage={}, path={}, dest={}",
149+
device_id,
150+
storage_id,
151+
object_path,
152+
local_dest.display()
153+
);
154+
155+
// Try to list the path as a directory first
156+
let entries = self.list_directory(device_id, storage_id, object_path).await;
157+
158+
match entries {
159+
Ok(entries) if !entries.is_empty() => {
160+
// It's a directory with contents - create local directory and download contents
161+
debug!(
162+
"MTP download_recursive: {} is a directory with {} entries",
163+
object_path,
164+
entries.len()
165+
);
166+
167+
tokio::fs::create_dir_all(local_dest)
168+
.await
169+
.map_err(|e| MtpConnectionError::Other {
170+
device_id: device_id.to_string(),
171+
message: format!("Failed to create local directory: {}", e),
172+
})?;
173+
174+
let mut total_bytes = 0u64;
175+
for entry in entries {
176+
let child_dest = local_dest.join(&entry.name);
177+
let bytes =
178+
Box::pin(self.download_recursive(device_id, storage_id, &entry.path, &child_dest)).await?;
179+
total_bytes += bytes;
180+
}
181+
182+
debug!(
183+
"MTP download_recursive: directory {} complete, {} bytes",
184+
object_path, total_bytes
185+
);
186+
Ok(total_bytes)
187+
}
188+
Ok(_) => {
189+
// Empty directory or file - check if it's a file by checking parent listing
190+
let path_buf = normalize_mtp_path(object_path);
191+
let is_file = if let Some(parent) = path_buf.parent() {
192+
let parent_str = parent.to_string_lossy();
193+
if let Ok(parent_entries) = self.list_directory(device_id, storage_id, &parent_str).await {
194+
if let Some(name) = path_buf.file_name().and_then(|n| n.to_str()) {
195+
parent_entries
196+
.iter()
197+
.find(|e| e.name == name)
198+
.is_some_and(|e| !e.is_directory)
199+
} else {
200+
false
201+
}
202+
} else {
203+
false
204+
}
205+
} else {
206+
false
207+
};
208+
209+
if is_file {
210+
// It's a file - download it
211+
debug!("MTP download_recursive: {} is a file, downloading", object_path);
212+
let operation_id = format!("download-{}", uuid::Uuid::new_v4());
213+
let result = self
214+
.download_file(device_id, storage_id, object_path, local_dest, None, &operation_id)
215+
.await?;
216+
Ok(result.bytes_transferred)
217+
} else {
218+
// Empty directory - create it
219+
debug!("MTP download_recursive: {} is an empty directory", object_path);
220+
tokio::fs::create_dir_all(local_dest)
221+
.await
222+
.map_err(|e| MtpConnectionError::Other {
223+
device_id: device_id.to_string(),
224+
message: format!("Failed to create local directory: {}", e),
225+
})?;
226+
Ok(0)
227+
}
228+
}
229+
Err(e) => {
230+
// list_directory failed - might be a file (MTP returns ObjectNotFound when
231+
// trying to list children of a file). Try to check by listing the parent.
232+
debug!(
233+
"MTP download_recursive: list failed for '{}', checking if it's a file: {:?}",
234+
object_path, e
235+
);
236+
237+
let path_buf = normalize_mtp_path(object_path);
238+
let is_file = if let Some(parent) = path_buf.parent() {
239+
let parent_str = parent.to_string_lossy();
240+
if let Ok(parent_entries) = self.list_directory(device_id, storage_id, &parent_str).await {
241+
if let Some(name) = path_buf.file_name().and_then(|n| n.to_str()) {
242+
parent_entries
243+
.iter()
244+
.find(|e| e.name == name)
245+
.is_some_and(|entry| !entry.is_directory)
246+
} else {
247+
false
248+
}
249+
} else {
250+
false
251+
}
252+
} else {
253+
false
254+
};
255+
256+
if is_file {
257+
debug!("MTP download_recursive: {} is a file, downloading", object_path);
258+
let operation_id = format!("download-{}", uuid::Uuid::new_v4());
259+
let result = self
260+
.download_file(device_id, storage_id, object_path, local_dest, None, &operation_id)
261+
.await?;
262+
Ok(result.bytes_transferred)
263+
} else {
264+
// Not a file, propagate the original error
265+
Err(e)
266+
}
267+
}
268+
}
269+
}
270+
271+
/// Uploads a file or directory from local filesystem to MTP device recursively.
272+
///
273+
/// If the source is a directory, creates the directory on the device and
274+
/// recursively uploads all contents.
275+
///
276+
/// # Arguments
277+
///
278+
/// * `device_id` - The connected device ID
279+
/// * `storage_id` - The storage ID within the device
280+
/// * `local_source` - Local source path (file or directory)
281+
/// * `dest_folder` - Destination folder path on device
282+
///
283+
/// # Returns
284+
///
285+
/// Total bytes transferred.
286+
pub async fn upload_recursive(
287+
&self,
288+
device_id: &str,
289+
storage_id: u32,
290+
local_source: &Path,
291+
dest_folder: &str,
292+
) -> Result<u64, MtpConnectionError> {
293+
debug!(
294+
"MTP upload_recursive: device={}, storage={}, source={}, dest={}",
295+
device_id,
296+
storage_id,
297+
local_source.display(),
298+
dest_folder
299+
);
300+
301+
let metadata = tokio::fs::metadata(local_source)
302+
.await
303+
.map_err(|e| MtpConnectionError::Other {
304+
device_id: device_id.to_string(),
305+
message: format!("Failed to read local path: {}", e),
306+
})?;
307+
308+
if metadata.is_file() {
309+
// Upload single file
310+
let operation_id = format!("upload-{}", uuid::Uuid::new_v4());
311+
let result = self
312+
.upload_file(device_id, storage_id, local_source, dest_folder, None, &operation_id)
313+
.await?;
314+
Ok(result.size.unwrap_or(0))
315+
} else if metadata.is_dir() {
316+
// Create directory on device and upload contents
317+
let dir_name = local_source
318+
.file_name()
319+
.ok_or_else(|| MtpConnectionError::Other {
320+
device_id: device_id.to_string(),
321+
message: "Invalid directory path".to_string(),
322+
})?
323+
.to_string_lossy()
324+
.to_string();
325+
326+
// Create the directory on the device
327+
let new_folder = self
328+
.create_folder(device_id, storage_id, dest_folder, &dir_name)
329+
.await?;
330+
let new_folder_path = new_folder.path;
331+
332+
// Upload all contents
333+
let mut total_bytes = 0u64;
334+
let mut entries = tokio::fs::read_dir(local_source)
335+
.await
336+
.map_err(|e| MtpConnectionError::Other {
337+
device_id: device_id.to_string(),
338+
message: format!("Failed to read local directory: {}", e),
339+
})?;
340+
341+
while let Some(entry) = entries.next_entry().await.map_err(|e| MtpConnectionError::Other {
342+
device_id: device_id.to_string(),
343+
message: format!("Failed to read directory entry: {}", e),
344+
})? {
345+
let entry_path = entry.path();
346+
let bytes =
347+
Box::pin(self.upload_recursive(device_id, storage_id, &entry_path, &new_folder_path)).await?;
348+
total_bytes += bytes;
349+
}
350+
351+
debug!(
352+
"MTP upload_recursive: directory {} complete, {} bytes",
353+
local_source.display(),
354+
total_bytes
355+
);
356+
Ok(total_bytes)
357+
} else {
358+
// Not a file or directory (symlink, etc.) - skip
359+
debug!(
360+
"MTP upload_recursive: skipping non-file/non-directory: {}",
361+
local_source.display()
362+
);
363+
Ok(0)
364+
}
365+
}
366+
}

0 commit comments

Comments
 (0)