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}