mtp_rs/mtp/storage.rs
1//! Storage operations (a thin façade over the active backend).
2
3use crate::cancel::{bail_if_cancelled, CancelToken};
4use crate::mtp::backend::{
5 BackendListing, BackendListingError, ByteRange, ListingErrorDisposition, MtpBackend, ProgressFn,
6};
7use crate::mtp::object::NewObjectInfo;
8use crate::mtp::stream::{FileDownload, Progress, WindowedDownload, DEFAULT_DOWNLOAD_WINDOW};
9use crate::mtp::{Error, ObjectHandle, ObjectInfo, StorageId, StorageInfo, UploadError};
10use bytes::Bytes;
11use futures::{Stream, StreamExt};
12use std::ops::ControlFlow;
13use std::sync::Arc;
14
15/// An in-progress directory listing that yields [`ObjectInfo`] items one at a time.
16///
17/// Created by [`Storage::list_objects_stream()`]. After the device returns the handle list, the
18/// total count is known immediately ([`total()`](Self::total)). Each call to [`next()`](Self::next)
19/// fetches one object's metadata, so the consumer can report progress (e.g.,
20/// "Loading files (42 of 500)...") as items arrive.
21///
22/// # Important
23///
24/// The device is busy while this listing is active. You must consume all items (or drop the
25/// listing) before calling other storage methods.
26///
27/// # Example
28///
29/// ```rust,no_run
30/// use mtp_rs::mtp::{ListingItem, MtpDevice};
31///
32/// # async fn example() -> Result<(), mtp_rs::Error> {
33/// # let device = MtpDevice::open_first().await?;
34/// # let storages = device.storages().await?;
35/// # let storage = &storages[0];
36/// let mut listing = storage.list_objects_stream(None).await?;
37/// println!("Loading {} files...", listing.total());
38///
39/// while let Some(item) = listing.next().await {
40/// match item? {
41/// ListingItem::Object(info) => {
42/// println!("[{}/{}] {}", listing.fetched(), listing.total(), info.filename);
43/// }
44/// ListingItem::Skipped(skipped) => {
45/// eprintln!("could not read handle {}: {}", skipped.handle.0, skipped.error);
46/// }
47/// }
48/// }
49/// # Ok(())
50/// # }
51/// ```
52pub struct ObjectListing {
53 inner: BackendListing,
54 /// Items the backend has already yielded (post-filter).
55 fetched: usize,
56}
57
58impl ObjectListing {
59 fn new(inner: BackendListing) -> Self {
60 Self { inner, fetched: 0 }
61 }
62
63 /// Total number of object handles returned by the device.
64 ///
65 /// When a parent filter is active (e.g. devices that return all objects for root), some items
66 /// may be skipped, so the actual yielded count can be lower.
67 #[must_use]
68 pub fn total(&self) -> usize {
69 self.inner.total
70 }
71
72 /// Number of items yielded so far.
73 #[must_use]
74 pub fn fetched(&self) -> usize {
75 self.fetched
76 }
77
78 async fn next_classified(&mut self) -> Option<Result<ObjectInfo, BackendListingError>> {
79 match self.inner.items.next().await {
80 Some(Ok(info)) => {
81 self.fetched += 1;
82 Some(Ok(info))
83 }
84 other => other,
85 }
86 }
87
88 /// Fetch the next item from the device.
89 ///
90 /// Returns `None` when the listing is exhausted. Items that don't match the parent filter are
91 /// skipped by the backend and never surface here.
92 ///
93 /// The `Ok` side has two shapes, and the distinction is the whole point: a
94 /// [`ListingItem::Object`] is an object whose metadata was read, and a
95 /// [`ListingItem::Skipped`] is one handle the device refused in a way that leaves the rest of
96 /// the listing usable (see [`Storage::collect_objects`] for what qualifies). An `Err` means the
97 /// listing itself is over: transport trouble, a broken session, cancellation, a malformed
98 /// response.
99 ///
100 /// So `Err` is "stop", `Ok(Skipped)` is "this one is unreadable, keep going", and you can't
101 /// confuse them by accident. Consumers that don't care can filter:
102 ///
103 /// ```no_run
104 /// # use mtp_rs::{ListingItem, Storage};
105 /// # async fn demo(storage: &Storage) -> Result<(), mtp_rs::Error> {
106 /// let mut listing = storage.list_objects_stream(None).await?;
107 /// while let Some(item) = listing.next().await {
108 /// if let ListingItem::Object(info) = item? {
109 /// println!("{}", info.filename);
110 /// }
111 /// }
112 /// # Ok(())
113 /// # }
114 /// ```
115 ///
116 /// If a [`CancelToken`] was passed via [`Storage::list_objects_stream_with_cancel`] and it's
117 /// been cancelled, this returns `Some(Err(Error::Cancelled))` at the next per-handle boundary.
118 pub async fn next(&mut self) -> Option<Result<ListingItem, Error>> {
119 match self.next_classified().await? {
120 Ok(info) => Some(Ok(ListingItem::Object(info))),
121 Err(error) if error.disposition == ListingErrorDisposition::SkipObject => {
122 Some(Ok(ListingItem::Skipped(SkippedObject {
123 handle: error.handle,
124 error: error.source,
125 })))
126 }
127 Err(error) => Some(Err(error.source)),
128 }
129 }
130}
131
132/// One item from an [`ObjectListing`].
133///
134/// The streaming and collecting APIs sit over the same stream, so they agree on what a per-object
135/// failure means: this enum is the streaming half of the [`ObjectCollection`] split.
136#[derive(Debug)]
137pub enum ListingItem {
138 /// An object whose metadata the device returned.
139 Object(ObjectInfo),
140 /// A handle the device refused, safely enough that the rest of the listing continues.
141 Skipped(SkippedObject),
142}
143
144impl ListingItem {
145 /// The object, or `None` if this item was skipped.
146 #[must_use]
147 pub fn object(self) -> Option<ObjectInfo> {
148 match self {
149 ListingItem::Object(info) => Some(info),
150 ListingItem::Skipped(_) => None,
151 }
152 }
153}
154
155/// A per-handle metadata failure that was safe to skip while reading the
156/// other objects in the same directory.
157#[derive(Debug)]
158pub struct SkippedObject {
159 /// The object handle whose metadata request failed.
160 pub handle: ObjectHandle,
161 /// The backend-neutral error reported for that handle.
162 pub error: Error,
163}
164
165/// A completed tolerant directory read: what was readable, and what wasn't.
166#[derive(Debug)]
167pub struct ObjectCollection {
168 /// Every object whose metadata was read successfully.
169 pub objects: Vec<ObjectInfo>,
170 /// Per-handle failures that met the library's narrow safe-to-skip policy.
171 pub skipped: Vec<SkippedObject>,
172}
173
174/// A storage location on an MTP device.
175///
176/// `Storage` holds a shared reference to the active backend so it can outlive the original
177/// `MtpDevice` and be used from multiple tasks.
178pub struct Storage {
179 backend: Arc<dyn MtpBackend>,
180 id: StorageId,
181 info: StorageInfo,
182}
183
184impl Storage {
185 /// Create a new Storage (internal).
186 pub(crate) fn new(backend: Arc<dyn MtpBackend>, id: StorageId, info: StorageInfo) -> Self {
187 Self { backend, id, info }
188 }
189
190 #[must_use]
191 pub fn id(&self) -> StorageId {
192 self.id
193 }
194
195 /// Storage information (cached, call refresh() to update).
196 #[must_use]
197 pub fn info(&self) -> &StorageInfo {
198 &self.info
199 }
200
201 /// Refresh storage info from device (updates free space, etc.).
202 pub async fn refresh(&mut self) -> Result<(), Error> {
203 self.info = self.backend.storage_info(self.id).await?;
204 Ok(())
205 }
206
207 // =========================================================================
208 // Listing
209 // =========================================================================
210
211 /// List objects in a folder (None = root), returning all results at once.
212 ///
213 /// For progress reporting during large listings, use
214 /// [`list_objects_stream()`](Self::list_objects_stream) instead.
215 ///
216 /// The backend handles device quirks (root-listing fast path and Android/Samsung/Fuji
217 /// fallbacks).
218 ///
219 /// A narrowly tolerated per-object metadata rejection does not hide valid
220 /// siblings. Use [`collect_objects`](Self::collect_objects) to
221 /// retain its handle and diagnostic, or the streaming API to observe every
222 /// item error directly. All errors that can compromise enumeration or
223 /// session integrity remain fatal.
224 pub async fn list_objects(
225 &self,
226 parent: Option<ObjectHandle>,
227 ) -> Result<Vec<ObjectInfo>, Error> {
228 self.list_objects_with_cancel(parent, None).await
229 }
230
231 /// Like [`list_objects`](Self::list_objects), but takes a cooperative cancellation token.
232 ///
233 /// When `cancel` is `Some(&token)` and the token has been cancelled, the call bails between
234 /// per-handle fetches with `Err(Error::Cancelled)`. Useful for large folders (1k+ entries on
235 /// Android), where the per-handle loop dominates wall-clock time.
236 pub async fn list_objects_with_cancel(
237 &self,
238 parent: Option<ObjectHandle>,
239 cancel: Option<&CancelToken>,
240 ) -> Result<Vec<ObjectInfo>, Error> {
241 Ok(self
242 .collect_objects_with_cancel(parent, cancel)
243 .await?
244 .objects)
245 }
246
247 /// Read a folder, keeping both the objects and a record of the handles that
248 /// couldn't be read.
249 ///
250 /// [`list_objects`](Self::list_objects) is the same read with the record
251 /// thrown away. Use this one when you need to tell "the folder has 49 files"
252 /// from "the folder has 49 files and a 50th we couldn't see", which is the
253 /// difference between a correct file listing and a silent omission.
254 ///
255 /// # What counts as skippable
256 ///
257 /// A per-handle failure may be skipped only when all three hold:
258 ///
259 /// 1. The handle list is already in hand, so the folder's membership isn't in
260 /// doubt, only one entry's metadata.
261 /// 2. The failing operation is read-only, so nothing on the device changed.
262 /// 3. The device answered with a protocol response code, which closes that
263 /// transaction cleanly and leaves the session usable for the next handle.
264 ///
265 /// Today exactly one case qualifies: a `GeneralError` response to
266 /// `GetObjectInfo` (Sphaira on the Nintendo Switch does this for one handle
267 /// out of 50). The rule is written down rather than the code, so adding a
268 /// second response code is a one-line change once a real device justifies it.
269 /// Nothing gets added speculatively.
270 ///
271 /// Everything else stays fatal: transport and session failures, malformed
272 /// responses, cancellation, stale handles, and any failure to enumerate the
273 /// handles in the first place. And if *every* handle was skipped, that's a
274 /// device that answered nothing, so this reports the failure rather than an
275 /// empty folder.
276 pub async fn collect_objects(
277 &self,
278 parent: Option<ObjectHandle>,
279 ) -> Result<ObjectCollection, Error> {
280 self.collect_objects_with_cancel(parent, None).await
281 }
282
283 /// Like [`collect_objects`](Self::collect_objects), but with a cooperative
284 /// cancellation token.
285 pub async fn collect_objects_with_cancel(
286 &self,
287 parent: Option<ObjectHandle>,
288 cancel: Option<&CancelToken>,
289 ) -> Result<ObjectCollection, Error> {
290 let mut listing = self.list_objects_stream_with_cancel(parent, cancel).await?;
291 let mut objects = Vec::with_capacity(listing.total());
292 let mut skipped = Vec::new();
293 while let Some(result) = listing.next_classified().await {
294 match result {
295 Ok(object) => objects.push(object),
296 Err(error) if error.disposition == ListingErrorDisposition::SkipObject => {
297 diag_debug!(
298 "list_objects: skipping handle {} on storage {} after a completed per-object metadata error: {}",
299 error.handle.0,
300 self.id.0,
301 error.source
302 );
303 skipped.push(SkippedObject {
304 handle: error.handle,
305 error: error.source,
306 });
307 }
308 Err(error) => return Err(error.source),
309 }
310 }
311
312 // Tolerating one bad object is the point. Reporting a device that answered
313 // NOTHING as an empty folder is not: `Ok(vec![])` renders as "empty folder"
314 // in a file manager and reads as "everything was deleted" to anything
315 // syncing, which turns a read failure into data loss. The device gave us
316 // handles and then failed every single lookup, so we learned nothing about
317 // a folder we know has contents. That's a failure, not a result.
318 if objects.is_empty() && !skipped.is_empty() {
319 let first = skipped.swap_remove(0);
320 diag_debug!(
321 "list_objects: every one of {} handles on storage {} failed its metadata lookup; \
322 reporting the failure rather than an empty folder",
323 skipped.len() + 1,
324 self.id.0
325 );
326 return Err(first.error);
327 }
328
329 Ok(ObjectCollection { objects, skipped })
330 }
331
332 /// List objects in a folder as a streaming [`ObjectListing`].
333 ///
334 /// Returns immediately after the device returns the handle list. The total count is then known
335 /// via [`ObjectListing::total()`], and each call to [`ObjectListing::next()`] fetches one
336 /// object's metadata.
337 ///
338 /// # Example
339 ///
340 /// ```rust,no_run
341 /// use mtp_rs::mtp::{ListingItem, MtpDevice};
342 ///
343 /// # async fn example() -> Result<(), mtp_rs::Error> {
344 /// # let device = MtpDevice::open_first().await?;
345 /// # let storages = device.storages().await?;
346 /// # let storage = &storages[0];
347 /// let mut listing = storage.list_objects_stream(None).await?;
348 /// println!("Found {} items", listing.total());
349 ///
350 /// while let Some(item) = listing.next().await {
351 /// if let ListingItem::Object(info) = item? {
352 /// println!("[{}/{}] {}", listing.fetched(), listing.total(), info.filename);
353 /// }
354 /// }
355 /// # Ok(())
356 /// # }
357 /// ```
358 pub async fn list_objects_stream(
359 &self,
360 parent: Option<ObjectHandle>,
361 ) -> Result<ObjectListing, Error> {
362 self.list_objects_stream_with_cancel(parent, None).await
363 }
364
365 /// Like [`list_objects_stream`](Self::list_objects_stream), but the returned [`ObjectListing`]
366 /// carries an optional [`CancelToken`]. Every call to [`ObjectListing::next`] checks the token
367 /// before issuing the next metadata roundtrip, so a flipped token bails within one roundtrip's
368 /// worth of latency instead of running to completion.
369 pub async fn list_objects_stream_with_cancel(
370 &self,
371 parent: Option<ObjectHandle>,
372 cancel: Option<&CancelToken>,
373 ) -> Result<ObjectListing, Error> {
374 let listing = self.backend.list(self.id, parent, cancel).await?;
375 Ok(ObjectListing::new(listing))
376 }
377
378 /// List objects recursively.
379 ///
380 /// Walks the folder tree manually via [`collect_objects`](Self::collect_objects), which already
381 /// applies the backend's root/quirk handling. Works the same across all devices, including
382 /// Android (whose native `GetObjectHandles` recursion is broken).
383 ///
384 /// Unreadable handles are dropped. Over a whole tree that can add up quietly, so use
385 /// [`collect_objects_recursive`](Self::collect_objects_recursive) when you need to know.
386 pub async fn list_objects_recursive(
387 &self,
388 parent: Option<ObjectHandle>,
389 ) -> Result<Vec<ObjectInfo>, Error> {
390 Ok(self.collect_objects_recursive(parent).await?.objects)
391 }
392
393 /// Walk a folder tree, keeping both the objects and every handle that couldn't be read.
394 ///
395 /// The recursive counterpart of [`collect_objects`](Self::collect_objects). One unreadable
396 /// object per folder is easy to shrug off; across a few thousand folders it's a silent
397 /// omission nobody notices, so the skips are aggregated across the whole walk rather than
398 /// dropped per folder.
399 pub async fn collect_objects_recursive(
400 &self,
401 parent: Option<ObjectHandle>,
402 ) -> Result<ObjectCollection, Error> {
403 let mut objects = Vec::new();
404 let mut skipped = Vec::new();
405 let mut folders_to_visit = vec![parent];
406
407 while let Some(current_parent) = folders_to_visit.pop() {
408 let collection = self.collect_objects(current_parent).await?;
409 skipped.extend(collection.skipped);
410 for obj in collection.objects {
411 if obj.is_folder() {
412 folders_to_visit.push(Some(obj.handle));
413 }
414 objects.push(obj);
415 }
416 }
417 Ok(ObjectCollection { objects, skipped })
418 }
419
420 /// Get object metadata by handle.
421 ///
422 /// Files larger than 4 GB have their u64 size auto-resolved by the backend.
423 pub async fn get_object_info(&self, handle: ObjectHandle) -> Result<ObjectInfo, Error> {
424 self.backend.object_info(handle).await
425 }
426
427 // =========================================================================
428 // Download operations
429 // =========================================================================
430
431 /// Download a whole file and return all bytes.
432 ///
433 /// For small to medium files where you want all the data in memory. For large files or
434 /// streaming to disk, use [`download`](Self::download).
435 pub async fn download_to_vec(&self, handle: ObjectHandle) -> Result<Vec<u8>, Error> {
436 self.backend.read_range(handle, 0, None).await
437 }
438
439 /// Read a bounded byte range into a `Vec<u8>` (single shot, buffered).
440 ///
441 /// Uses the device's 64-bit partial-read operation, so offsets beyond 4 GB work on devices that
442 /// advertise it. `len` is capped at `u32::MAX` per call.
443 pub async fn read_range(
444 &self,
445 handle: ObjectHandle,
446 offset: u64,
447 len: u32,
448 ) -> Result<Vec<u8>, Error> {
449 self.backend.read_range(handle, offset, Some(len)).await
450 }
451
452 /// Fetch the thumbnail image bytes for an object.
453 pub async fn thumbnail(&self, handle: ObjectHandle) -> Result<Vec<u8>, Error> {
454 self.backend.thumbnail(handle).await
455 }
456
457 /// Download a file as a stream (true streaming), holding the session for the whole file.
458 ///
459 /// Yields data chunks as they arrive without buffering the entire file in memory. This is the
460 /// raw-speed path; it holds the device's one session open for the whole file (see [`download`]
461 /// docs). For a long read where the device must stay responsive to other work, use
462 /// [`download_windowed`](Self::download_windowed) instead.
463 ///
464 /// # Resume on forward-only-seek devices
465 ///
466 /// A [`ByteRange::From`]/[`ByteRange::Range`] resume assumes the device can seek to the offset
467 /// cheaply. The Windows WPD backend's Pixel-class devices return `E_NOTIMPL` from `IStream::Seek`,
468 /// so the backend reaches the offset by reading and discarding the prefix: a resume is O(offset)
469 /// and re-streams every byte before the offset. Resuming near the end of a large file re-reads
470 /// almost the whole file, so prefer a single in-order pass over many small offset resumes there.
471 ///
472 /// [`download`]: Self::download
473 pub async fn download(
474 &self,
475 handle: ObjectHandle,
476 range: ByteRange,
477 ) -> Result<FileDownload, Error> {
478 let dl = self.backend.download(handle, range).await?;
479 Ok(FileDownload::new(dl.size, dl.body))
480 }
481
482 /// Read a large file as a sequence of bounded windows, **freeing the session between every
483 /// window** so the device stays responsive.
484 ///
485 /// Each [`next_window()`](WindowedDownload::next_window) is a single bounded read that completes
486 /// and releases the device. Between two `next_window()` calls a consumer can interleave other
487 /// device work (service a pending folder listing, navigate, check a cancel flag) without
488 /// aborting the read.
489 ///
490 /// `window_size` is the maximum bytes per window. [`DEFAULT_DOWNLOAD_WINDOW`] (8 MiB) is a
491 /// documented suggestion; a `window_size` of 0 is clamped to 1.
492 ///
493 /// # Resume on forward-only-seek devices
494 ///
495 /// A windowed *resume* from an offset (`ByteRange::From`/`Range`) re-reads the skipped prefix on
496 /// devices whose `IStream::Seek` is `E_NOTIMPL` (the Windows WPD backend's Pixel-class devices),
497 /// making the first window after the offset O(offset). The session-freeing benefit between windows
498 /// still holds, but starting deep into a large file pays a full re-read of the prefix first;
499 /// prefer covering the file from the start (`ByteRange::Full`) where possible.
500 pub async fn download_windowed(
501 &self,
502 handle: ObjectHandle,
503 range: ByteRange,
504 window_size: u32,
505 ) -> Result<WindowedDownload, Error> {
506 let size = self.backend.object_info(handle).await?.size;
507 let offset = range.offset();
508 if offset > size {
509 return Err(Error::invalid_data(format!(
510 "windowed download offset {offset} is past the object size {size}"
511 )));
512 }
513 Ok(WindowedDownload::new(
514 Arc::clone(&self.backend),
515 handle,
516 size,
517 offset,
518 window_size,
519 ))
520 }
521
522 /// Read a large file in windows using the default window size
523 /// ([`DEFAULT_DOWNLOAD_WINDOW`], 8 MiB), covering the whole file.
524 pub async fn download_windowed_default(
525 &self,
526 handle: ObjectHandle,
527 ) -> Result<WindowedDownload, Error> {
528 self.download_windowed(handle, ByteRange::Full, DEFAULT_DOWNLOAD_WINDOW)
529 .await
530 }
531
532 // =========================================================================
533 // Upload operations
534 // =========================================================================
535
536 /// Upload a file from a stream.
537 ///
538 /// The data streams directly to the device in chunks; the protocol only needs the total size
539 /// upfront (provided via `info`), not the whole file in memory.
540 ///
541 /// # Errors
542 ///
543 /// Returns [`UploadError`] on failure. Uploads are two-phase: the object is created (yielding a
544 /// handle), then the bytes are streamed. If the data phase fails, the device may keep a partial
545 /// object, and [`UploadError::partial`] carries its handle so you can [`delete`](Self::delete)
546 /// it or retry the data phase to resume. The library does **not** auto-delete it.
547 pub async fn upload<'a, S>(
548 &'a self,
549 parent: Option<ObjectHandle>,
550 info: NewObjectInfo,
551 data: S,
552 ) -> Result<ObjectHandle, UploadError>
553 where
554 S: Stream<Item = Result<Bytes, std::io::Error>> + Unpin + Send + 'a,
555 {
556 self.backend
557 .upload(self.id, parent, info, Box::pin(data), None)
558 .await
559 }
560
561 /// Upload a file with a progress callback.
562 ///
563 /// Progress is reported as data is read from the stream. Return `ControlFlow::Break(())` from
564 /// the callback to cancel the upload (which surfaces as [`Error::Cancelled`] in
565 /// [`UploadError::source`]).
566 pub async fn upload_with_progress<'a, S, F>(
567 &'a self,
568 parent: Option<ObjectHandle>,
569 info: NewObjectInfo,
570 data: S,
571 on_progress: F,
572 ) -> Result<ObjectHandle, UploadError>
573 where
574 S: Stream<Item = Result<Bytes, std::io::Error>> + Unpin + Send + 'a,
575 F: FnMut(Progress) -> ControlFlow<()> + Send + 'a,
576 {
577 let progress: ProgressFn<'a> = Box::new(on_progress);
578 self.backend
579 .upload(self.id, parent, info, Box::pin(data), Some(progress))
580 .await
581 }
582
583 // =========================================================================
584 // Folder and object management
585 // =========================================================================
586
587 pub async fn create_folder(
588 &self,
589 parent: Option<ObjectHandle>,
590 name: &str,
591 ) -> Result<ObjectHandle, Error> {
592 self.backend.create_folder(self.id, parent, name).await
593 }
594
595 pub async fn delete(&self, handle: ObjectHandle) -> Result<(), Error> {
596 self.backend.delete(handle, None).await
597 }
598
599 /// Like [`delete`](Self::delete), but bails with `Err(Error::Cancelled)` before issuing the
600 /// delete request when the token is set.
601 pub async fn delete_with_cancel(
602 &self,
603 handle: ObjectHandle,
604 cancel: Option<&CancelToken>,
605 ) -> Result<(), Error> {
606 bail_if_cancelled(cancel)?;
607 self.backend.delete(handle, cancel).await
608 }
609
610 /// Move an object to a different folder (optionally a different storage).
611 pub async fn move_object(
612 &self,
613 handle: ObjectHandle,
614 new_parent: ObjectHandle,
615 new_storage: Option<StorageId>,
616 ) -> Result<(), Error> {
617 let storage = new_storage.unwrap_or(self.id);
618 self.backend.move_object(handle, new_parent, storage).await
619 }
620
621 pub async fn copy_object(
622 &self,
623 handle: ObjectHandle,
624 new_parent: ObjectHandle,
625 new_storage: Option<StorageId>,
626 ) -> Result<ObjectHandle, Error> {
627 let storage = new_storage.unwrap_or(self.id);
628 self.backend.copy_object(handle, new_parent, storage).await
629 }
630
631 /// Rename an object (file or folder).
632 ///
633 /// Not all devices support renaming. Use `MtpDevice::supports_rename()` to check.
634 pub async fn rename(&self, handle: ObjectHandle, new_name: &str) -> Result<(), Error> {
635 self.backend.rename(handle, new_name).await
636 }
637}