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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! An interface for managing and monitoring BITS jobs.
//!
//! BITS is a Windows service for performing downloads in the background, independent from an
//! application, usually via HTTP/HTTPS.
//!
//! [`BitsClient`](enum.BitsClient.html) is the main interface, used to issue commands.
//!
//! [`BitsMonitorClient`](enum.BitsMonitorClient.html) delivers periodic status reports about a
//! job.
//!
//! Microsoft's documentation for BITS can be found at
//! <https://docs.microsoft.com/en-us/windows/desktop/Bits/background-intelligent-transfer-service-portal>
extern crate bits;
extern crate comedy;
extern crate guid_win;
extern crate thiserror;
pub mod bits_protocol;
mod in_process;
use std::ffi;
use bits_protocol::*;
use thiserror::Error;
pub use bits::status::{BitsErrorContext, BitsJobState, BitsJobTimes};
pub use bits::{BitsJobProgress, BitsJobStatus, BitsProxyUsage};
pub use bits_protocol::{JobError, JobStatus};
pub use comedy::HResult;
pub use guid_win::Guid;
// These errors would come from a Local Service client but are mostly unused currently.
// PipeError properly lives in the crate that deals with named pipes, but it isn't in use now.
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum PipeError {
#[error("Pipe is not connected")]
NotConnected,
#[error("Operation timed out")]
Timeout,
#[error("Should have written {0} bytes, wrote {1}")]
WriteCount(usize, u32),
#[error("Windows API error")]
Api(#[from] HResult),
}
pub use PipeError as Error;
/// A client for interacting with BITS.
///
/// Methods on `BitsClient` return a `Result<Result<_, XyzFailure>, Error>`. The outer `Result`
/// is `Err` if there was a communication error in sending the associated command or receiving
/// its response. Currently this is always `Ok` as all clients are in-process. The inner
/// `Result` is `Err` if there was an error executing the command.
///
/// A single `BitsClient` can be used with multiple BITS jobs simultaneously; generally a job
/// is not bound tightly to a client.
///
/// A `BitsClient` tracks all [`BitsMonitorClient`s](enum.BitsMonitorClient.html) that it started
/// with `start_job()` or `monitor_job()`, so that the monitor can be stopped or modified.
pub enum BitsClient {
// The `InProcess` variant does all BITS calls directly.
#[doc(hidden)]
InProcess(in_process::InProcessClient),
// Space is reserved here for the LocalService variant, which will work through an external
// process running as Local Service.
}
use BitsClient::InProcess;
impl BitsClient {
/// Create an in-process `BitsClient`.
///
/// `job_name` will be used when creating jobs, and this `BitsClient` can only be used to
/// manipulate jobs with that name.
///
/// `save_path_prefix` will be prepended to the local `save_path` given to `start_job()`, it
/// must name an existing directory.
pub fn new(
job_name: ffi::OsString,
save_path_prefix: ffi::OsString,
) -> Result<BitsClient, Error> {
Ok(InProcess(in_process::InProcessClient::new(
job_name,
save_path_prefix,
)?))
}
/// Start a job to download a single file at `url` to local path `save_path` (relative to the
/// `save_path_prefix` given when constructing the `BitsClient`).
///
/// `save_path_prefix` combined with `save_path` must name a file (existing or not) in an
/// existing directory, which must be under the directory named by `save_path_prefix`.
///
/// `proxy_usage` determines what proxy will be used.
///
/// When a successful result `Ok(result)` is returned, `result.0.guid` is the id for the
/// new job, and `result.1` is a monitor client that can be polled for periodic updates,
/// returning a result approximately once per `monitor_interval_millis` milliseconds.
pub fn start_job(
&mut self,
url: ffi::OsString,
save_path: ffi::OsString,
proxy_usage: BitsProxyUsage,
no_progress_timeout_secs: u32,
monitor_interval_millis: u32,
) -> Result<Result<(StartJobSuccess, BitsMonitorClient), StartJobFailure>, Error> {
match self {
InProcess(client) => Ok(client
.start_job(
url,
save_path,
proxy_usage,
no_progress_timeout_secs,
monitor_interval_millis,
)
.map(|(success, monitor)| (success, BitsMonitorClient::InProcess(monitor)))),
}
}
/// Start monitoring the job with id `guid` approximately once per `monitor_interval_millis`
/// milliseconds.
///
/// The returned `Ok(monitor)` is a monitor client to be polled for periodic updates.
///
/// There can only be one ongoing `BitsMonitorClient` for each job associated with a given
/// `BitsClient`. If a monitor client already exists for the specified job, it will be stopped.
pub fn monitor_job(
&mut self,
guid: Guid,
interval_millis: u32,
) -> Result<Result<BitsMonitorClient, MonitorJobFailure>, Error> {
match self {
InProcess(client) => Ok(client
.monitor_job(guid, interval_millis)
.map(BitsMonitorClient::InProcess)),
}
}
/// Suspend job `guid`.
pub fn suspend_job(&mut self, guid: Guid) -> Result<Result<(), SuspendJobFailure>, Error> {
match self {
InProcess(client) => Ok(client.suspend_job(guid)),
}
}
/// Resume job `guid`.
pub fn resume_job(&mut self, guid: Guid) -> Result<Result<(), ResumeJobFailure>, Error> {
match self {
InProcess(client) => Ok(client.resume_job(guid)),
}
}
/// Set the priority of job `guid`.
///
/// `foreground == true` will set the priority to `BG_JOB_PRIORITY_FOREGROUND`,
/// `false` will use the default `BG_JOB_PRIORITY_NORMAL`.
/// See the Microsoft documentation for `BG_JOB_PRIORITY` for details.
///
/// A job created by `start_job()` will be foreground priority, by default.
pub fn set_job_priority(
&mut self,
guid: Guid,
foreground: bool,
) -> Result<Result<(), SetJobPriorityFailure>, Error> {
match self {
InProcess(client) => Ok(client.set_job_priority(guid, foreground)),
}
}
/// Set the "no progress timeout" of job `guid`.
pub fn set_no_progress_timeout(
&mut self,
guid: Guid,
timeout_secs: u32,
) -> Result<Result<(), SetNoProgressTimeoutFailure>, Error> {
match self {
InProcess(client) => Ok(client.set_no_progress_timeout(guid, timeout_secs)),
}
}
/// Change the update interval for an ongoing monitor of job `guid`.
pub fn set_update_interval(
&mut self,
guid: Guid,
interval_millis: u32,
) -> Result<Result<(), SetUpdateIntervalFailure>, Error> {
match self {
InProcess(client) => Ok(client.set_update_interval(guid, interval_millis)),
}
}
/// Stop any ongoing monitor for job `guid`.
pub fn stop_update(
&mut self,
guid: Guid,
) -> Result<Result<(), SetUpdateIntervalFailure>, Error> {
match self {
InProcess(client) => Ok(client.stop_update(guid)),
}
}
/// Complete the job `guid`.
///
/// This also stops any ongoing monitor for the job.
pub fn complete_job(&mut self, guid: Guid) -> Result<Result<(), CompleteJobFailure>, Error> {
match self {
InProcess(client) => Ok(client.complete_job(guid)),
}
}
/// Cancel the job `guid`.
///
/// This also stops any ongoing monitor for the job.
pub fn cancel_job(&mut self, guid: Guid) -> Result<Result<(), CancelJobFailure>, Error> {
match self {
InProcess(client) => Ok(client.cancel_job(guid)),
}
}
}
/// The client side of a monitor for a BITS job.
///
/// It is intended to be used by calling `get_status` in a loop to receive notifications about
/// the status of a job. Because `get_status` blocks, it is recommended to run this loop on its
/// own thread.
pub enum BitsMonitorClient {
InProcess(in_process::InProcessMonitor),
}
impl BitsMonitorClient {
/// `get_status` will return a result approximately every `monitor_interval_millis`
/// milliseconds, but in case a result isn't available within `timeout_millis` milliseconds
/// this will return `Err(Error::Timeout)`. Any `Err` returned, including timeout, indicates
/// that the monitor has been stopped; the `BitsMonitorClient` should then be discarded.
///
/// As with methods on `BitsClient`, `BitsMonitorClient::get_status()` has an inner `Result`
/// type which indicates an error returned from the server. Any `Err` here also indicates that
/// the monitor has stopped after yielding the result.
///
/// The first time `get_status` is called it will return a status without any delay.
///
/// If there is an error or the transfer completes, a result may be available sooner than
/// the monitor interval.
pub fn get_status(
&mut self,
timeout_millis: u32,
) -> Result<Result<JobStatus, HResultMessage>, Error> {
match self {
BitsMonitorClient::InProcess(client) => client.get_status(timeout_millis),
}
}
}
|