Skip to main content

mtp_rs/mtp/backend/
mod.rs

1//! The backend seam for the high-level [`crate::mtp`] API.
2//!
3//! [`MtpBackend`] is the one abstraction every concrete portable-device backend implements in
4//! backend-neutral vocabulary (neutral [`crate::mtp`] types and [`crate::mtp::Error`]). The
5//! PTP-over-USB backend ([`UsbBackend`]) is the sole implementation today; a Windows WPD-over-COM
6//! backend is planned (see `docs/windows-wpd-backend-plan.md`). [`crate::mtp::MtpDevice`] and
7//! [`crate::mtp::Storage`] are thin concrete façades over a `Box<dyn MtpBackend>`, so consumers
8//! never see the trait or generics.
9//!
10//! A trait (not enum dispatch) keeps each backend self-contained in its own module and makes a
11//! future backend a new file rather than edits to every method; the per-call dynamic dispatch is
12//! noise against USB/COM latency.
13
14pub(crate) mod usb;
15
16#[cfg(windows)]
17pub(crate) mod wpd;
18
19use crate::cancel::CancelToken;
20use crate::mtp::object::NewObjectInfo;
21use crate::mtp::stream::Progress;
22use crate::mtp::{
23    Capabilities, DeviceEvent, DeviceInfo, Error, ObjectHandle, ObjectInfo, StorageId, StorageInfo,
24    UploadError,
25};
26use async_trait::async_trait;
27use bytes::Bytes;
28use futures::Stream;
29use std::ops::ControlFlow;
30use std::pin::Pin;
31use std::time::Duration;
32
33/// Selects which backend [`MtpDeviceBuilder`](crate::mtp::MtpDeviceBuilder) opens.
34///
35/// `Auto` (the default) picks per platform: on Windows it prefers the WPD backend (phones are bound
36/// to the WPD driver, not WinUSB), falling back to PTP-over-USB only if no WPD device is present;
37/// on other platforms it uses USB. `Usb` forces PTP-over-USB (e.g. a Zadig/WinUSB-bound camera on
38/// Windows); `Wpd` forces Windows WPD-over-COM (and errors as unsupported off Windows).
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum Backend {
41    /// Platform default: Windows → WPD then USB; elsewhere → USB.
42    #[default]
43    Auto,
44    /// Force PTP-over-USB.
45    Usb,
46    /// Force Windows WPD-over-COM.
47    Wpd,
48}
49
50/// Which bytes of an object a download should cover.
51///
52/// Used by the backend's download / read-range primitives to express whole-file, resume, and
53/// bounded-window reads with one type. The façade's download conveniences all desugar to this.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum ByteRange {
56    /// The whole object, `[0, size)`.
57    Full,
58    /// From `offset` to end-of-file, `[offset, size)`.
59    From(u64),
60    /// A bounded slice `[offset, offset + len)` (clamped to the object size by the backend).
61    Range {
62        /// Start byte offset.
63        offset: u64,
64        /// Number of bytes.
65        len: u64,
66    },
67}
68
69impl ByteRange {
70    /// The starting byte offset of this range.
71    #[must_use]
72    pub(crate) fn offset(self) -> u64 {
73        match self {
74            ByteRange::Full => 0,
75            ByteRange::From(offset) | ByteRange::Range { offset, .. } => offset,
76        }
77    }
78}
79
80/// A progress callback for uploads. Returning [`ControlFlow::Break`] cancels the transfer.
81///
82/// Lifetime-parameterized (not `'static`) so a consumer can pass a callback that borrows local
83/// state — the upload is awaited to completion within the call, so the borrow need only outlive
84/// that call.
85pub(crate) type ProgressFn<'a> = Box<dyn FnMut(Progress) -> ControlFlow<()> + Send + 'a>;
86
87/// A boxed, backend-neutral stream of upload data chunks.
88pub(crate) type UploadStream<'a> =
89    Pin<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send + 'a>>;
90
91/// One in-progress streaming download, holding whatever resource the backend needs for the whole
92/// transfer (the USB backend holds the PTP session open). The façade's [`crate::mtp::FileDownload`]
93/// wraps this; consumers don't see it.
94#[async_trait]
95pub(crate) trait DownloadBody: Send {
96    /// The next chunk of data, or `None` at end-of-file.
97    async fn next_chunk(&mut self) -> Option<Result<Bytes, Error>>;
98
99    /// Cancel the in-flight download, releasing the backend resource and leaving the device usable
100    /// for the next operation. A no-op if already complete.
101    async fn cancel(&mut self, idle_timeout: Duration) -> Result<(), Error>;
102}
103
104/// One streaming download returned by [`MtpBackend::download`]: the full object size plus the body.
105pub(crate) struct BackendDownload {
106    /// Full object size in bytes (always the whole file, even for an offset/range read).
107    pub(crate) size: u64,
108    /// The download body.
109    pub(crate) body: Box<dyn DownloadBody>,
110}
111
112/// A boxed stream of object metadata, yielded by [`MtpBackend::list`].
113pub(crate) type ObjectStream = Pin<Box<dyn Stream<Item = Result<ObjectInfo, Error>> + Send>>;
114
115/// One in-progress listing returned by [`MtpBackend::list`]: a known total plus the item stream.
116pub(crate) struct BackendListing {
117    /// Total number of handles the device reported (before any parent filtering).
118    pub(crate) total: usize,
119    /// The metadata stream.
120    pub(crate) items: ObjectStream,
121}
122
123/// The backend-neutral portable-device API. See the module docs.
124#[async_trait]
125pub(crate) trait MtpBackend: Send + Sync {
126    /// Cached device identity.
127    fn device_info(&self) -> &DeviceInfo;
128
129    /// What the device supports (derived per backend).
130    fn capabilities(&self) -> &Capabilities;
131
132    /// All storages on the device.
133    async fn storages(&self) -> Result<Vec<StorageInfo>, Error>;
134
135    /// Fetch info for a single storage by id.
136    async fn storage_info(&self, storage: StorageId) -> Result<StorageInfo, Error>;
137
138    /// List the children of `parent` on `storage` (cancellable, streaming).
139    async fn list(
140        &self,
141        storage: StorageId,
142        parent: Option<ObjectHandle>,
143        cancel: Option<&CancelToken>,
144    ) -> Result<BackendListing, Error>;
145
146    /// Metadata for one object (with the full >4 GB size resolved).
147    async fn object_info(&self, obj: ObjectHandle) -> Result<ObjectInfo, Error>;
148
149    /// A streaming download of `obj` over `range`. Holds the backend resource for the transfer.
150    async fn download(&self, obj: ObjectHandle, range: ByteRange)
151        -> Result<BackendDownload, Error>;
152
153    /// A single-shot buffered read of `[offset, offset+len)` (or to EOF when `len` is `None`).
154    async fn read_range(
155        &self,
156        obj: ObjectHandle,
157        offset: u64,
158        len: Option<u32>,
159    ) -> Result<Vec<u8>, Error>;
160
161    /// Fetch the thumbnail bytes for `obj`.
162    async fn thumbnail(&self, obj: ObjectHandle) -> Result<Vec<u8>, Error>;
163
164    /// Create a new object under `parent` on `storage` and stream its data.
165    async fn upload(
166        &self,
167        storage: StorageId,
168        parent: Option<ObjectHandle>,
169        info: NewObjectInfo,
170        data: UploadStream<'_>,
171        progress: Option<ProgressFn<'_>>,
172    ) -> Result<ObjectHandle, UploadError>;
173
174    /// Create a folder named `name` under `parent` on `storage`.
175    async fn create_folder(
176        &self,
177        storage: StorageId,
178        parent: Option<ObjectHandle>,
179        name: &str,
180    ) -> Result<ObjectHandle, Error>;
181
182    /// Delete `obj` (cancellable before the request is issued).
183    async fn delete(&self, obj: ObjectHandle, cancel: Option<&CancelToken>) -> Result<(), Error>;
184
185    /// Move `obj` to `new_parent` (optionally on `new_storage`).
186    async fn move_object(
187        &self,
188        obj: ObjectHandle,
189        new_parent: ObjectHandle,
190        new_storage: StorageId,
191    ) -> Result<(), Error>;
192
193    /// Copy `obj` to `new_parent` (optionally on `new_storage`), returning the copy's handle.
194    async fn copy_object(
195        &self,
196        obj: ObjectHandle,
197        new_parent: ObjectHandle,
198        new_storage: StorageId,
199    ) -> Result<ObjectHandle, Error>;
200
201    /// Rename `obj`.
202    async fn rename(&self, obj: ObjectHandle, new_name: &str) -> Result<(), Error>;
203
204    /// Await the next device event (indefinitely; wrap in a timeout).
205    async fn next_event(&self) -> Result<DeviceEvent, Error>;
206
207    /// Close the connection (best-effort). Also happens on drop of the backing resource.
208    async fn close(&self) -> Result<(), Error>;
209}