summaryrefslogtreecommitdiffstats
path: root/toolkit/components/bitsdownload/bits_client/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/bitsdownload/bits_client/src/lib.rs')
-rw-r--r--toolkit/components/bitsdownload/bits_client/src/lib.rs258
1 files changed, 258 insertions, 0 deletions
diff --git a/toolkit/components/bitsdownload/bits_client/src/lib.rs b/toolkit/components/bitsdownload/bits_client/src/lib.rs
new file mode 100644
index 0000000000..7db887bbc5
--- /dev/null
+++ b/toolkit/components/bitsdownload/bits_client/src/lib.rs
@@ -0,0 +1,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),
+ }
+ }
+}