summaryrefslogtreecommitdiffstats
path: root/toolkit/components/bitsdownload/bits_client/bits
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/bitsdownload/bits_client/bits
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/bitsdownload/bits_client/bits')
-rw-r--r--toolkit/components/bitsdownload/bits_client/bits/Cargo.toml31
-rw-r--r--toolkit/components/bitsdownload/bits_client/bits/src/callback.rs205
-rw-r--r--toolkit/components/bitsdownload/bits_client/bits/src/lib.rs592
-rw-r--r--toolkit/components/bitsdownload/bits_client/bits/src/status.rs118
-rw-r--r--toolkit/components/bitsdownload/bits_client/bits/src/wide.rs38
5 files changed, 984 insertions, 0 deletions
diff --git a/toolkit/components/bitsdownload/bits_client/bits/Cargo.toml b/toolkit/components/bitsdownload/bits_client/bits/Cargo.toml
new file mode 100644
index 0000000000..36b7b5ff89
--- /dev/null
+++ b/toolkit/components/bitsdownload/bits_client/bits/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "bits"
+version = "0.2.0"
+authors = ["Adam Gashlin <agashlin@mozilla.com>"]
+license = "MIT/Apache-2.0"
+publish = false
+
+[features]
+status_serde = ["serde", "serde_derive"]
+
+[dependencies]
+comedy = "0.2.0"
+filetime_win = "0.2.0"
+guid_win = "0.2.0"
+serde = { version = "1.0.80", optional = true }
+serde_derive = { version = "1.0.80", optional = true }
+
+[dependencies.winapi]
+version = "0.3.7"
+features = ["basetsd",
+ "bits",
+ "bits2_5",
+ "bitsmsg",
+ "guiddef",
+ "minwindef",
+ "ntdef",
+ "rpcndr",
+ "unknwnbase",
+ "winerror",
+ "winnls",
+ ]
diff --git a/toolkit/components/bitsdownload/bits_client/bits/src/callback.rs b/toolkit/components/bitsdownload/bits_client/bits/src/callback.rs
new file mode 100644
index 0000000000..6dace83be8
--- /dev/null
+++ b/toolkit/components/bitsdownload/bits_client/bits/src/callback.rs
@@ -0,0 +1,205 @@
+// Licensed under the Apache License, Version 2.0
+// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// All files in the project carrying such notice may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::panic::{catch_unwind, RefUnwindSafe};
+use std::ptr::NonNull;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+use comedy::{com::ComRef, HResult};
+use guid_win::Guid;
+use winapi::ctypes::c_void;
+use winapi::shared::guiddef::REFIID;
+use winapi::shared::minwindef::DWORD;
+use winapi::shared::ntdef::ULONG;
+use winapi::shared::winerror::{E_FAIL, E_NOINTERFACE, HRESULT, NOERROR, S_OK};
+use winapi::um::bits::{
+ IBackgroundCopyCallback, IBackgroundCopyCallbackVtbl, IBackgroundCopyError, IBackgroundCopyJob,
+};
+use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl};
+use winapi::Interface;
+
+use BitsJob;
+
+/// The type of a notification callback.
+///
+/// The callbacks must be `Fn()` to be called arbitrarily many times, `RefUnwindSafe` to have a
+/// panic unwind safely caught, `Send`, `Sync` and `'static` to run on any thread COM invokes us on
+/// any time.
+///
+/// If the callback returns a non-success `HRESULT`, the notification may pass to other BITS
+/// mechanisms such as `IBackgroundCopyJob2::SetNotifyCmdLine`.
+pub type TransferredCallback =
+ dyn (Fn() -> Result<(), HRESULT>) + RefUnwindSafe + Send + Sync + 'static;
+pub type ErrorCallback = dyn (Fn() -> Result<(), HRESULT>) + RefUnwindSafe + Send + Sync + 'static;
+pub type ModificationCallback =
+ dyn (Fn() -> Result<(), HRESULT>) + RefUnwindSafe + Send + Sync + 'static;
+
+#[repr(C)]
+pub struct BackgroundCopyCallback {
+ // Everything assumes that the interface vtable is the first member of this struct.
+ interface: IBackgroundCopyCallback,
+ rc: AtomicUsize,
+ transferred_cb: Option<Box<TransferredCallback>>,
+ error_cb: Option<Box<ErrorCallback>>,
+ modification_cb: Option<Box<ModificationCallback>>,
+}
+
+impl BackgroundCopyCallback {
+ /// Construct the callback object and register it with a job.
+ ///
+ /// Only one notify interface can be present on a job at once, so this will release BITS'
+ /// ref to any previously registered interface.
+ pub fn register(
+ job: &mut BitsJob,
+ transferred_cb: Option<Box<TransferredCallback>>,
+ error_cb: Option<Box<ErrorCallback>>,
+ modification_cb: Option<Box<ModificationCallback>>,
+ ) -> Result<(), HResult> {
+ let cb = Box::new(BackgroundCopyCallback {
+ interface: IBackgroundCopyCallback { lpVtbl: &VTBL },
+ rc: AtomicUsize::new(1),
+ transferred_cb,
+ error_cb,
+ modification_cb,
+ });
+
+ // Leak the callback, it has no Rust owner until we need to drop it later.
+ // The ComRef will Release when it goes out of scope.
+ unsafe {
+ let cb = ComRef::from_raw(NonNull::new_unchecked(Box::into_raw(cb) as *mut IUnknown));
+
+ job.set_notify_interface(cb.as_raw_ptr())?;
+ }
+
+ Ok(())
+ }
+}
+
+extern "system" fn query_interface(
+ this: *mut IUnknown,
+ riid: REFIID,
+ obj: *mut *mut c_void,
+) -> HRESULT {
+ unsafe {
+ // `IBackgroundCopyCallback` is the first (currently only) interface on the
+ // `BackgroundCopyCallback` object, so we can return `this` either as
+ // `IUnknown` or `IBackgroundCopyCallback`.
+ if Guid(*riid) == Guid(IUnknown::uuidof())
+ || Guid(*riid) == Guid(IBackgroundCopyCallback::uuidof())
+ {
+ addref(this);
+ // Cast first to `IBackgroundCopyCallback` to be clear which `IUnknown`
+ // we are pointing at.
+ *obj = this as *mut IBackgroundCopyCallback as *mut c_void;
+ NOERROR
+ } else {
+ E_NOINTERFACE
+ }
+ }
+}
+
+extern "system" fn addref(raw_this: *mut IUnknown) -> ULONG {
+ unsafe {
+ let this = raw_this as *const BackgroundCopyCallback;
+
+ // Forge a reference for just this statement.
+ let old_rc = (*this).rc.fetch_add(1, Ordering::SeqCst);
+ (old_rc + 1) as ULONG
+ }
+}
+
+extern "system" fn release(raw_this: *mut IUnknown) -> ULONG {
+ unsafe {
+ {
+ let this = raw_this as *const BackgroundCopyCallback;
+
+ // Forge a reference for just this statement.
+ let old_rc = (*this).rc.fetch_sub(1, Ordering::SeqCst);
+
+ let rc = old_rc - 1;
+ if rc > 0 {
+ return rc as ULONG;
+ }
+ }
+
+ // rc will have been 0 for us to get here, and we're out of scope of the reference above,
+ // so there should be no references or pointers left (besides `this`).
+ // Re-Box and to drop immediately.
+ let _ = Box::from_raw(raw_this as *mut BackgroundCopyCallback);
+
+ 0
+ }
+}
+
+extern "system" fn transferred_stub(
+ raw_this: *mut IBackgroundCopyCallback,
+ _job: *mut IBackgroundCopyJob,
+) -> HRESULT {
+ unsafe {
+ let this = raw_this as *const BackgroundCopyCallback;
+ // Forge a reference just for this statement.
+ if let Some(ref cb) = (*this).transferred_cb {
+ match catch_unwind(|| cb()) {
+ Ok(Ok(())) => S_OK,
+ Ok(Err(hr)) => hr,
+ Err(_) => E_FAIL,
+ }
+ } else {
+ S_OK
+ }
+ }
+}
+
+extern "system" fn error_stub(
+ raw_this: *mut IBackgroundCopyCallback,
+ _job: *mut IBackgroundCopyJob,
+ _error: *mut IBackgroundCopyError,
+) -> HRESULT {
+ unsafe {
+ let this = raw_this as *const BackgroundCopyCallback;
+ // Forge a reference just for this statement.
+ if let Some(ref cb) = (*this).error_cb {
+ match catch_unwind(|| cb()) {
+ Ok(Ok(())) => S_OK,
+ Ok(Err(hr)) => hr,
+ Err(_) => E_FAIL,
+ }
+ } else {
+ S_OK
+ }
+ }
+}
+
+extern "system" fn modification_stub(
+ raw_this: *mut IBackgroundCopyCallback,
+ _job: *mut IBackgroundCopyJob,
+ _reserved: DWORD,
+) -> HRESULT {
+ unsafe {
+ let this = raw_this as *const BackgroundCopyCallback;
+ // Forge a reference just for this statement.
+ if let Some(ref cb) = (*this).modification_cb {
+ match catch_unwind(|| cb()) {
+ Ok(Ok(())) => S_OK,
+ Ok(Err(hr)) => hr,
+ Err(_) => E_FAIL,
+ }
+ } else {
+ S_OK
+ }
+ }
+}
+
+pub static VTBL: IBackgroundCopyCallbackVtbl = IBackgroundCopyCallbackVtbl {
+ parent: IUnknownVtbl {
+ QueryInterface: query_interface,
+ AddRef: addref,
+ Release: release,
+ },
+ JobTransferred: transferred_stub,
+ JobError: error_stub,
+ JobModification: modification_stub,
+};
diff --git a/toolkit/components/bitsdownload/bits_client/bits/src/lib.rs b/toolkit/components/bitsdownload/bits_client/bits/src/lib.rs
new file mode 100644
index 0000000000..09da2b0349
--- /dev/null
+++ b/toolkit/components/bitsdownload/bits_client/bits/src/lib.rs
@@ -0,0 +1,592 @@
+// Licensed under the Apache License, Version 2.0
+// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// All files in the project carrying such notice may not be copied, modified, or distributed
+// except according to those terms.
+
+//! A safe interface for BITS
+//!
+//! The primary entry point into BITS is the
+//! [`BackgroundCopyManager`](struct.BackgroundCopyManager.html) struct.
+//!
+//! Functionality is only provided by this crate on an as-needed basis for
+//! [bits_client](../bits_client/index.html), so there are vast swathes of the BITS API
+//! unsupported.
+
+extern crate comedy;
+extern crate filetime_win;
+extern crate guid_win;
+extern crate winapi;
+
+#[cfg(feature = "status_serde")]
+extern crate serde;
+#[cfg(feature = "status_serde")]
+extern crate serde_derive;
+
+mod callback;
+pub mod status;
+mod wide;
+
+use std::ffi::{OsStr, OsString};
+use std::mem;
+use std::os::windows::ffi::OsStringExt;
+use std::ptr;
+use std::result;
+
+use comedy::com::{create_instance_local_server, CoTaskMem, ComRef, INIT_MTA};
+use comedy::error::{HResult, ResultExt};
+use comedy::{com_call, com_call_getter, com_call_taskmem_getter};
+use filetime_win::FileTime;
+use guid_win::Guid;
+use winapi::shared::minwindef::DWORD;
+use winapi::shared::ntdef::{HRESULT, LANGIDFROMLCID, ULONG};
+use winapi::shared::winerror::S_FALSE;
+use winapi::um::bits::{
+ IBackgroundCopyError, IBackgroundCopyFile, IBackgroundCopyJob, IBackgroundCopyManager,
+ IEnumBackgroundCopyFiles, IEnumBackgroundCopyJobs, BG_JOB_PRIORITY, BG_JOB_PRIORITY_FOREGROUND,
+ BG_JOB_PRIORITY_HIGH, BG_JOB_PRIORITY_LOW, BG_JOB_PRIORITY_NORMAL, BG_JOB_PROXY_USAGE,
+ BG_JOB_PROXY_USAGE_AUTODETECT, BG_JOB_PROXY_USAGE_NO_PROXY, BG_JOB_PROXY_USAGE_PRECONFIG,
+ BG_JOB_STATE_ERROR, BG_JOB_STATE_TRANSIENT_ERROR, BG_JOB_TYPE_DOWNLOAD, BG_NOTIFY_DISABLE,
+ BG_NOTIFY_JOB_ERROR, BG_NOTIFY_JOB_MODIFICATION, BG_NOTIFY_JOB_TRANSFERRED, BG_SIZE_UNKNOWN,
+};
+use winapi::um::bits2_5::{IBackgroundCopyJobHttpOptions, BG_HTTP_REDIRECT_POLICY_ALLOW_REPORT};
+use winapi::um::bitsmsg::BG_E_NOT_FOUND;
+use winapi::um::unknwnbase::IUnknown;
+use winapi::um::winnls::GetThreadLocale;
+
+pub use winapi::um::bits::{BG_ERROR_CONTEXT, BG_JOB_STATE};
+pub use winapi::um::bitsmsg::{BG_S_PARTIAL_COMPLETE, BG_S_UNABLE_TO_DELETE_FILES};
+
+pub use status::{
+ BitsErrorContext, BitsJobError, BitsJobProgress, BitsJobState, BitsJobStatus, BitsJobTimes,
+};
+use wide::ToWideNull;
+
+pub use winapi::shared::winerror::E_FAIL;
+
+#[repr(u32)]
+#[derive(Copy, Clone, Debug)]
+pub enum BitsJobPriority {
+ Foreground = BG_JOB_PRIORITY_FOREGROUND,
+ High = BG_JOB_PRIORITY_HIGH,
+ /// Default
+ Normal = BG_JOB_PRIORITY_NORMAL,
+ Low = BG_JOB_PRIORITY_LOW,
+}
+
+#[repr(u32)]
+#[derive(Copy, Clone, Debug)]
+pub enum BitsProxyUsage {
+ /// Directly access the network.
+ NoProxy = BG_JOB_PROXY_USAGE_NO_PROXY,
+ /// Use Internet Explorer proxy settings. This is the default.
+ Preconfig = BG_JOB_PROXY_USAGE_PRECONFIG,
+ /// Attempt to auto-detect the connection's proxy settings.
+ AutoDetect = BG_JOB_PROXY_USAGE_AUTODETECT,
+}
+
+type Result<T> = result::Result<T, HResult>;
+
+pub struct BackgroundCopyManager(ComRef<IBackgroundCopyManager>);
+
+impl BackgroundCopyManager {
+ /// Get access to the local BITS service.
+ ///
+ /// # COM Initialization and Threading Model #
+ ///
+ /// This method uses a thread local variable to initialize COM with a multithreaded apartment
+ /// model for this thread, and leaves it this way until the thread local is dropped.
+ /// If the thread was in a single-threaded apartment, `connect()` will fail gracefully.
+ ///
+ /// # Safety #
+ ///
+ /// If there are mismatched `CoUninitialize` calls on this thread which lead to COM shutting
+ /// down before this thread ends, unsafe behavior may result.
+ pub fn connect() -> Result<BackgroundCopyManager> {
+ INIT_MTA.with(|com| {
+ if let Err(e) = com {
+ return Err(e.clone());
+ }
+ Ok(())
+ })?;
+
+ // Assuming no mismatched CoUninitialize calls, methods do not have to check for
+ // successfully initialized COM once the object is constructed: `BackgroundCopyManager`
+ // is not `Send` or `Sync` so it must be used on the thread it was constructed on,
+ // which has now successfully inited MTA for the lifetime of thread local `INIT_MTA`.
+ // This also holds for any functions using pointers only derived from these methods, like
+ // the `BitsJob` methods.
+
+ Ok(BackgroundCopyManager(create_instance_local_server::<
+ winapi::um::bits::BackgroundCopyManager,
+ IBackgroundCopyManager,
+ >()?))
+ }
+
+ /// Create a new download job with the given name.
+ pub fn create_job(&self, display_name: &OsStr) -> Result<BitsJob> {
+ unsafe {
+ let mut guid = mem::zeroed();
+ Ok(BitsJob(com_call_getter!(
+ |job| self.0,
+ IBackgroundCopyManager::CreateJob(
+ display_name.to_wide_null().as_ptr(),
+ BG_JOB_TYPE_DOWNLOAD,
+ &mut guid,
+ job,
+ )
+ )?))
+ }
+ }
+
+ /// Cancel all jobs with the given name.
+ ///
+ /// This only attempts to cancel jobs owned by the current user.
+ /// No errors are returned for jobs that failed to cancel.
+ pub fn cancel_jobs_by_name(&self, match_name: &OsStr) -> Result<()> {
+ let jobs =
+ unsafe { com_call_getter!(|jobs| self.0, IBackgroundCopyManager::EnumJobs(0, jobs))? };
+
+ loop {
+ let result = unsafe {
+ com_call_getter!(
+ |job| jobs,
+ IEnumBackgroundCopyJobs::Next(1, job, ptr::null_mut())
+ )
+ };
+ match result {
+ Ok(job) => {
+ if job_name_eq(&job, match_name)? {
+ unsafe {
+ let _ = com_call!(job, IBackgroundCopyJob::Cancel());
+ }
+ }
+ }
+ Err(e) => {
+ if e.code() == S_FALSE {
+ // Ran out of jobs to enumerate
+ return Ok(());
+ } else {
+ return Err(e);
+ }
+ }
+ }
+ }
+ }
+
+ /// Get the job with the given GUID.
+ ///
+ /// Returns Err if the job was not found.
+ pub fn get_job_by_guid(&self, guid: &Guid) -> Result<BitsJob> {
+ unsafe { com_call_getter!(|job| self.0, IBackgroundCopyManager::GetJob(&guid.0, job)) }
+ .map(BitsJob)
+ }
+
+ /// Try to find a job with a given GUID.
+ ///
+ /// Returns Ok(None) if the job was not found but there was no other error.
+ pub fn find_job_by_guid(&self, guid: &Guid) -> Result<Option<BitsJob>> {
+ Ok(self
+ .get_job_by_guid(guid)
+ .map(Some)
+ .allow_err(BG_E_NOT_FOUND as i32, None)?)
+ }
+
+ /// Try to find a job with a given GUID and name.
+ ///
+ /// Returns Ok(None) if the job was not found, or if it had the wrong name, as long as there
+ /// was no other error.
+ pub fn find_job_by_guid_and_name(
+ &self,
+ guid: &Guid,
+ match_name: &OsStr,
+ ) -> Result<Option<BitsJob>> {
+ Ok(match self.find_job_by_guid(guid)? {
+ None => None,
+ Some(BitsJob(ref job)) if !job_name_eq(job, match_name)? => None,
+ result => result,
+ })
+ }
+
+ /// Translate a BITS `HRESULT` to a textual description.
+ ///
+ /// This uses the current thread's locale to look up the message associated with a BITS
+ /// error. It should only be used for `HRESULT`s returned from BITS COM interfaces.
+ pub fn get_error_description(&self, hr: HRESULT) -> Result<String> {
+ unsafe {
+ let language_id = DWORD::from(LANGIDFROMLCID(GetThreadLocale()));
+
+ Ok(taskmem_into_lossy_string(com_call_taskmem_getter!(
+ |desc| self.0,
+ IBackgroundCopyManager::GetErrorDescription(hr, language_id, desc)
+ )?))
+ }
+ }
+}
+
+unsafe fn taskmem_into_lossy_string(taskmem: CoTaskMem<u16>) -> String {
+ OsString::from_wide(taskmem.as_slice_until_null())
+ .to_string_lossy()
+ .into_owned()
+}
+
+fn job_name_eq(job: &ComRef<IBackgroundCopyJob>, match_name: &OsStr) -> Result<bool> {
+ let job_name = unsafe {
+ OsString::from_wide(
+ com_call_taskmem_getter!(|name| job, IBackgroundCopyJob::GetDisplayName(name))?
+ .as_slice_until_null(),
+ )
+ };
+
+ Ok(job_name == match_name)
+}
+
+pub struct BitsJob(ComRef<IBackgroundCopyJob>);
+
+impl BitsJob {
+ /// Get the job's GUID.
+ pub fn guid(&self) -> Result<Guid> {
+ // TODO: cache on create or retrieved by GUID?
+ unsafe {
+ let mut guid = mem::zeroed();
+ com_call!(self.0, IBackgroundCopyJob::GetId(&mut guid))?;
+ Ok(Guid(guid))
+ }
+ }
+
+ /// Add a file to the job.
+ pub fn add_file(&mut self, remote_url: &OsStr, local_file: &OsStr) -> Result<()> {
+ unsafe {
+ com_call!(
+ self.0,
+ IBackgroundCopyJob::AddFile(
+ remote_url.to_wide_null().as_ptr(),
+ local_file.to_wide_null().as_ptr(),
+ )
+ )
+ }?;
+ Ok(())
+ }
+
+ /// Get the first file in the job.
+ ///
+ /// This is provided for collecting the redirected remote name of single file jobs.
+ pub fn get_first_file(&mut self) -> Result<BitsFile> {
+ let files = unsafe { com_call_getter!(|e| self.0, IBackgroundCopyJob::EnumFiles(e))? };
+
+ let file = unsafe {
+ com_call_getter!(
+ |file| files,
+ IEnumBackgroundCopyFiles::Next(1, file, ptr::null_mut())
+ )?
+ };
+
+ Ok(BitsFile(file))
+ }
+
+ /// Set the job's description string.
+ ///
+ /// This is different from the display name set when creating the job.
+ pub fn set_description(&mut self, description: &OsStr) -> Result<()> {
+ unsafe {
+ com_call!(
+ self.0,
+ IBackgroundCopyJob::SetDescription(description.to_wide_null().as_ptr())
+ )
+ }?;
+ Ok(())
+ }
+
+ /// Change the job's proxy usage setting.
+ ///
+ /// The default is `BitsProxyUsage::Preconfig`.
+ pub fn set_proxy_usage(&mut self, usage: BitsProxyUsage) -> Result<()> {
+ use BitsProxyUsage::*;
+
+ match usage {
+ Preconfig | NoProxy | AutoDetect => {
+ unsafe {
+ com_call!(
+ self.0,
+ IBackgroundCopyJob::SetProxySettings(
+ usage as BG_JOB_PROXY_USAGE,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ )
+ )
+ }?;
+ Ok(())
+ }
+ }
+ }
+
+ /// Change the job's priority.
+ ///
+ /// The default is `BitsJobPriority::Normal`.
+ pub fn set_priority(&mut self, priority: BitsJobPriority) -> Result<()> {
+ unsafe {
+ com_call!(
+ self.0,
+ IBackgroundCopyJob::SetPriority(priority as BG_JOB_PRIORITY)
+ )
+ }?;
+ Ok(())
+ }
+
+ pub fn set_minimum_retry_delay(&mut self, seconds: ULONG) -> Result<()> {
+ unsafe { com_call!(self.0, IBackgroundCopyJob::SetMinimumRetryDelay(seconds)) }?;
+ Ok(())
+ }
+
+ pub fn set_no_progress_timeout(&mut self, seconds: ULONG) -> Result<()> {
+ unsafe { com_call!(self.0, IBackgroundCopyJob::SetNoProgressTimeout(seconds)) }?;
+ Ok(())
+ }
+
+ /// Enable HTTP redirect reporting.
+ ///
+ /// The default setting is to allow HTTP redirects, but to not report them in any way. With
+ /// this setting enabled, the remote name of a file will be updated to reflect the redirect.
+ ///
+ /// # Compatibility #
+ ///
+ /// First available in Windows Vista.
+ pub fn set_redirect_report(&mut self) -> Result<()> {
+ unsafe {
+ com_call!(
+ self.0.cast()?,
+ IBackgroundCopyJobHttpOptions::SetSecurityFlags(
+ BG_HTTP_REDIRECT_POLICY_ALLOW_REPORT
+ )
+ )
+ }?;
+
+ Ok(())
+ }
+
+ /// Resume the job. This must be done at least once to initially enqueue the job.
+ pub fn resume(&mut self) -> Result<()> {
+ unsafe { com_call!(self.0, IBackgroundCopyJob::Resume()) }?;
+ Ok(())
+ }
+
+ pub fn suspend(&mut self) -> Result<()> {
+ unsafe { com_call!(self.0, IBackgroundCopyJob::Suspend()) }?;
+ Ok(())
+ }
+
+ /// Complete the job, moving the local files to their final names.
+ ///
+ /// Has two interesting success `HRESULT`s: `BG_S_PARTIAL_COMPLETE` and
+ /// `BG_S_UNABLE_TO_DELETE_FILES`.
+ pub fn complete(&mut self) -> Result<HRESULT> {
+ unsafe { com_call!(self.0, IBackgroundCopyJob::Complete()) }
+ }
+
+ /// Cancel the job, deleting any temporary files.
+ ///
+ /// Has an interesting success `HRESULT`: `BG_S_UNABLE_TO_DELETE_FILES`.
+ pub fn cancel(&mut self) -> Result<HRESULT> {
+ unsafe { com_call!(self.0, IBackgroundCopyJob::Cancel()) }
+ }
+
+ /// Set the notification callbacks to use with this job.
+ ///
+ /// This will replace any previously set callbacks.
+ pub fn register_callbacks(
+ &mut self,
+ transferred_cb: Option<Box<callback::TransferredCallback>>,
+ error_cb: Option<Box<callback::ErrorCallback>>,
+ modification_cb: Option<Box<callback::ModificationCallback>>,
+ ) -> Result<()> {
+ let mut flags = 0;
+ if transferred_cb.is_some() {
+ flags |= BG_NOTIFY_JOB_TRANSFERRED;
+ }
+ if error_cb.is_some() {
+ flags |= BG_NOTIFY_JOB_ERROR;
+ }
+ if modification_cb.is_some() {
+ flags |= BG_NOTIFY_JOB_MODIFICATION;
+ }
+
+ callback::BackgroundCopyCallback::register(
+ self,
+ transferred_cb,
+ error_cb,
+ modification_cb,
+ )?;
+
+ unsafe { com_call!(self.0, IBackgroundCopyJob::SetNotifyFlags(flags)) }?;
+
+ Ok(())
+ }
+
+ fn _clear_callbacks(&mut self) -> Result<()> {
+ unsafe {
+ com_call!(
+ self.0,
+ IBackgroundCopyJob::SetNotifyFlags(BG_NOTIFY_DISABLE)
+ )?;
+
+ self.set_notify_interface(ptr::null_mut() as *mut IUnknown)
+ }
+ }
+
+ /// Collect the current status of the job, including errors.
+ pub fn get_status(&self) -> Result<BitsJobStatus> {
+ let mut state = 0;
+ let mut progress = unsafe { mem::zeroed() };
+ let mut error_count = 0;
+ let mut times = unsafe { mem::zeroed() };
+
+ unsafe {
+ com_call!(self.0, IBackgroundCopyJob::GetState(&mut state))?;
+ com_call!(self.0, IBackgroundCopyJob::GetProgress(&mut progress))?;
+ com_call!(self.0, IBackgroundCopyJob::GetErrorCount(&mut error_count))?;
+ com_call!(self.0, IBackgroundCopyJob::GetTimes(&mut times))?;
+ }
+
+ Ok(BitsJobStatus {
+ state: BitsJobState::from(state),
+ progress: BitsJobProgress {
+ total_bytes: if progress.BytesTotal == BG_SIZE_UNKNOWN {
+ None
+ } else {
+ Some(progress.BytesTotal)
+ },
+ transferred_bytes: progress.BytesTransferred,
+ total_files: progress.FilesTotal,
+ transferred_files: progress.FilesTransferred,
+ },
+ error_count,
+ error: if state == BG_JOB_STATE_ERROR || state == BG_JOB_STATE_TRANSIENT_ERROR {
+ let error_obj =
+ unsafe { com_call_getter!(|e| self.0, IBackgroundCopyJob::GetError(e)) }?;
+
+ Some(BitsJob::get_error(error_obj)?)
+ } else {
+ None
+ },
+ times: BitsJobTimes {
+ creation: FileTime(times.CreationTime),
+ modification: FileTime(times.ModificationTime),
+ transfer_completion: if times.TransferCompletionTime.dwLowDateTime == 0
+ && times.TransferCompletionTime.dwHighDateTime == 0
+ {
+ None
+ } else {
+ Some(FileTime(times.TransferCompletionTime))
+ },
+ },
+ })
+ }
+
+ fn get_error(error_obj: ComRef<IBackgroundCopyError>) -> Result<BitsJobError> {
+ let mut context = 0;
+ let mut hresult = 0;
+ unsafe {
+ com_call!(
+ error_obj,
+ IBackgroundCopyError::GetError(&mut context, &mut hresult)
+ )?;
+
+ let language_id = DWORD::from(LANGIDFROMLCID(GetThreadLocale()));
+
+ let context = BitsErrorContext::from(context);
+ let context_str = com_call_taskmem_getter!(
+ |desc| error_obj,
+ IBackgroundCopyError::GetErrorContextDescription(language_id, desc)
+ )
+ .map(|s| taskmem_into_lossy_string(s))
+ .unwrap_or_else(|_| format!("{:?}", context));
+ let error_str = com_call_taskmem_getter!(
+ |desc| error_obj,
+ IBackgroundCopyError::GetErrorDescription(language_id, desc)
+ )
+ .map(|s| taskmem_into_lossy_string(s))
+ .unwrap_or_else(|_| format!("{:#08x}", hresult));
+
+ Ok(BitsJobError {
+ context,
+ context_str,
+ error: hresult,
+ error_str,
+ })
+ }
+ }
+
+ unsafe fn set_notify_interface(&self, interface: *mut IUnknown) -> Result<()> {
+ com_call!(self.0, IBackgroundCopyJob::SetNotifyInterface(interface))?;
+ Ok(())
+ }
+}
+
+pub struct BitsFile(ComRef<IBackgroundCopyFile>);
+
+/// A single file in a BITS job.
+///
+/// This is provided for collecting the redirected remote name.
+impl BitsFile {
+ /// Get the remote name from which the file is being downloaded.
+ ///
+ /// If [`BitsJob::set_redirect_report()`](struct.BitsJob.html#method.set_redirect_report)
+ /// hasn't been called on the job, this won't be
+ /// updated as HTTP redirects are processed.
+ pub fn get_remote_name(&self) -> Result<OsString> {
+ unsafe {
+ Ok(OsString::from_wide(
+ com_call_taskmem_getter!(|name| self.0, IBackgroundCopyFile::GetRemoteName(name))?
+ .as_slice_until_null(),
+ ))
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::BackgroundCopyManager;
+ use std::ffi::OsString;
+ use std::mem;
+
+ #[test]
+ #[ignore]
+ fn test_find_job() {
+ let bcm = BackgroundCopyManager::connect().unwrap();
+ let name = OsString::from("bits test job");
+ let wrong_name = OsString::from("bits test jobbo");
+
+ let mut job = bcm.create_job(&name).unwrap();
+ let guid = job.guid().unwrap();
+
+ assert_eq!(
+ bcm.find_job_by_guid(&guid)
+ .unwrap()
+ .unwrap()
+ .guid()
+ .unwrap(),
+ guid
+ );
+ assert_eq!(
+ bcm.find_job_by_guid_and_name(&guid, &name)
+ .unwrap()
+ .unwrap()
+ .guid()
+ .unwrap(),
+ guid
+ );
+ assert!(bcm
+ .find_job_by_guid_and_name(&guid, &wrong_name)
+ .unwrap()
+ .is_none());
+
+ job.cancel().unwrap();
+ mem::drop(job);
+
+ assert!(bcm.find_job_by_guid(&guid).unwrap().is_none());
+ assert!(bcm
+ .find_job_by_guid_and_name(&guid, &name)
+ .unwrap()
+ .is_none());
+ }
+}
diff --git a/toolkit/components/bitsdownload/bits_client/bits/src/status.rs b/toolkit/components/bitsdownload/bits_client/bits/src/status.rs
new file mode 100644
index 0000000000..648a26866e
--- /dev/null
+++ b/toolkit/components/bitsdownload/bits_client/bits/src/status.rs
@@ -0,0 +1,118 @@
+// Licensed under the Apache License, Version 2.0
+// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// All files in the project carrying such notice may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Data types for reporting a job's status
+
+use filetime_win::FileTime;
+use winapi::shared::winerror::HRESULT;
+use winapi::um::bits::{BG_ERROR_CONTEXT, BG_JOB_STATE};
+
+#[cfg(feature = "status_serde")]
+use serde_derive::{Deserialize, Serialize};
+
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "status_serde", derive(Serialize, Deserialize))]
+pub struct BitsJobStatus {
+ pub state: BitsJobState,
+ pub progress: BitsJobProgress,
+ pub error_count: u32,
+ pub error: Option<BitsJobError>,
+ pub times: BitsJobTimes,
+}
+
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "status_serde", derive(Serialize, Deserialize))]
+pub struct BitsJobError {
+ pub context: BitsErrorContext,
+ pub context_str: String,
+ pub error: HRESULT,
+ pub error_str: String,
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "status_serde", derive(Serialize, Deserialize))]
+pub enum BitsErrorContext {
+ None,
+ Unknown,
+ GeneralQueueManager,
+ QueueManagerNotification,
+ LocalFile,
+ RemoteFile,
+ GeneralTransport,
+ RemoteApplication,
+ /// No other values are documented
+ Other(BG_ERROR_CONTEXT),
+}
+
+impl From<BG_ERROR_CONTEXT> for BitsErrorContext {
+ fn from(ec: BG_ERROR_CONTEXT) -> BitsErrorContext {
+ use self::BitsErrorContext::*;
+ use winapi::um::bits;
+ match ec {
+ bits::BG_ERROR_CONTEXT_NONE => None,
+ bits::BG_ERROR_CONTEXT_UNKNOWN => Unknown,
+ bits::BG_ERROR_CONTEXT_GENERAL_QUEUE_MANAGER => GeneralQueueManager,
+ bits::BG_ERROR_CONTEXT_QUEUE_MANAGER_NOTIFICATION => QueueManagerNotification,
+ bits::BG_ERROR_CONTEXT_LOCAL_FILE => LocalFile,
+ bits::BG_ERROR_CONTEXT_REMOTE_FILE => RemoteFile,
+ bits::BG_ERROR_CONTEXT_GENERAL_TRANSPORT => GeneralTransport,
+ bits::BG_ERROR_CONTEXT_REMOTE_APPLICATION => RemoteApplication,
+ ec => Other(ec),
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "status_serde", derive(Serialize, Deserialize))]
+pub enum BitsJobState {
+ Queued,
+ Connecting,
+ Transferring,
+ Suspended,
+ Error,
+ TransientError,
+ Transferred,
+ Acknowledged,
+ Cancelled,
+ /// No other values are documented
+ Other(BG_JOB_STATE),
+}
+
+impl From<BG_JOB_STATE> for BitsJobState {
+ fn from(s: BG_JOB_STATE) -> BitsJobState {
+ use self::BitsJobState::*;
+ use winapi::um::bits;
+ match s {
+ bits::BG_JOB_STATE_QUEUED => Queued,
+ bits::BG_JOB_STATE_CONNECTING => Connecting,
+ bits::BG_JOB_STATE_TRANSFERRING => Transferring,
+ bits::BG_JOB_STATE_SUSPENDED => Suspended,
+ bits::BG_JOB_STATE_ERROR => Error,
+ bits::BG_JOB_STATE_TRANSIENT_ERROR => TransientError,
+ bits::BG_JOB_STATE_TRANSFERRED => Transferred,
+ bits::BG_JOB_STATE_ACKNOWLEDGED => Acknowledged,
+ bits::BG_JOB_STATE_CANCELLED => Cancelled,
+ s => Other(s),
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "status_serde", derive(Serialize, Deserialize))]
+pub struct BitsJobProgress {
+ pub total_bytes: Option<u64>,
+ pub transferred_bytes: u64,
+ pub total_files: u32,
+ pub transferred_files: u32,
+}
+
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "status_serde", derive(Serialize, Deserialize))]
+pub struct BitsJobTimes {
+ pub creation: FileTime,
+ pub modification: FileTime,
+ pub transfer_completion: Option<FileTime>,
+}
diff --git a/toolkit/components/bitsdownload/bits_client/bits/src/wide.rs b/toolkit/components/bitsdownload/bits_client/bits/src/wide.rs
new file mode 100644
index 0000000000..c108f8d629
--- /dev/null
+++ b/toolkit/components/bitsdownload/bits_client/bits/src/wide.rs
@@ -0,0 +1,38 @@
+// Licensed under the Apache License, Version 2.0
+// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// All files in the project carrying such notice may not be copied, modified, or distributed
+// except according to those terms.
+
+// Minimal null-terminated wide string support from wio.
+
+use std::ffi::{OsStr, OsString};
+use std::os::windows::ffi::{OsStrExt, OsStringExt};
+use std::slice;
+
+pub trait ToWideNull {
+ fn to_wide_null(&self) -> Vec<u16>;
+}
+
+impl<T: AsRef<OsStr>> ToWideNull for T {
+ fn to_wide_null(&self) -> Vec<u16> {
+ self.as_ref().encode_wide().chain(Some(0)).collect()
+ }
+}
+
+pub trait FromWidePtrNull {
+ unsafe fn from_wide_ptr_null(wide: *const u16) -> Self;
+}
+
+impl FromWidePtrNull for OsString {
+ unsafe fn from_wide_ptr_null(wide: *const u16) -> Self {
+ assert!(!wide.is_null());
+
+ for i in 0.. {
+ if *wide.offset(i) == 0 {
+ return Self::from_wide(&slice::from_raw_parts(wide, i as usize));
+ }
+ }
+ unreachable!()
+ }
+}