1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
//! Cooperative cancellation for long-running MTP operations.
//!
//! [`CancelToken`] is a cheap-to-clone handle that callers pass into operations
//! like [`Storage::list_objects`](crate::mtp::Storage::list_objects) so they can
//! be aborted from another task. The operation checks the token between USB
//! roundtrips; when set, it returns [`Error::Cancelled`](crate::Error::Cancelled)
//! within one roundtrip's worth of latency instead of running to completion.
//!
//! # When to use this
//!
//! MTP list and recursive delete on large folders consist of many small
//! per-object USB transactions (`GetObjectInfo` per handle). A 1k-photo
//! `/DCIM/Camera` listing on Android can take 15+ seconds over USB 2.0. Without
//! cooperative cancel, a user-initiated cancel only stops issuing _new_
//! transactions after the current bulk call's _entire_ for-loop completes. By
//! then the device may already be wedged from a follow-up op.
//!
//! [`CancelToken`] gives every per-handle iteration a fast way to bail.
//!
//! # Mid-data-phase cancel
//!
//! For streaming downloads, see
//! [`FileDownload::cancel`](crate::mtp::FileDownload::cancel); that path uses
//! the USB Still Image Class (SIC) class-cancel mechanism to abort a long
//! bulk-IN transfer mid-stream. Per-handle list/delete operations don't need
//! that: each `GetObjectInfo` / `DeleteObject` USB roundtrip completes in
//! milliseconds, so checking the token between roundtrips is sufficient and
//! safe (no half-finished transfer to drain).
//!
//! # Example
//!
//! ```rust,no_run
//! use mtp_rs::{CancelToken, mtp::MtpDevice};
//!
//! # async fn example() -> Result<(), mtp_rs::Error> {
//! let device = MtpDevice::open_first().await?;
//! let storages = device.storages().await?;
//! let storage = &storages[0];
//!
//! let cancel = CancelToken::new();
//!
//! // Fire the cancel from another task after 500 ms.
//! let cancel_for_task = cancel.clone();
//! tokio::spawn(async move {
//! tokio::time::sleep(std::time::Duration::from_millis(500)).await;
//! cancel_for_task.cancel();
//! });
//!
//! match storage.list_objects_with_cancel(None, Some(&cancel)).await {
//! Ok(objects) => println!("Listed {} objects", objects.len()),
//! Err(mtp_rs::Error::Cancelled) => println!("Cancelled mid-listing"),
//! Err(e) => return Err(e),
//! }
//! # Ok(())
//! # }
//! ```
use ;
use Arc;
/// Cooperative cancellation handle for long-running MTP operations.
///
/// Cheap to clone (`Arc`-backed). `Send + Sync`. Once cancelled, the token
/// stays cancelled (no reset). Construct a fresh `CancelToken` per
/// logical operation.
///
/// See the [module-level docs](crate::cancel) for usage and semantics.
/// Helper: checks an optional cancel token and returns `Err(Error::Cancelled)`
/// when set. Use at iteration boundaries inside long operations.
pub