Skip to main content

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}