summaryrefslogtreecommitdiffstats
path: root/toolkit/components/bitsdownload/src
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/bitsdownload/src')
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/action.rs118
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/dispatch_callback.rs199
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/error.rs686
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/mod.rs329
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/monitor.rs247
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/request.rs763
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/string.rs80
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/task/client.rs102
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/task/from_threadbound.rs125
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/task/mod.rs18
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/task/request_task.rs425
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/task/service_task.rs332
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/xpcom_methods.rs195
-rw-r--r--toolkit/components/bitsdownload/src/lib.rs23
14 files changed, 3642 insertions, 0 deletions
diff --git a/toolkit/components/bitsdownload/src/bits_interface/action.rs b/toolkit/components/bitsdownload/src/bits_interface/action.rs
new file mode 100644
index 0000000000..1019e29827
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/action.rs
@@ -0,0 +1,118 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+//! `Action` is an enum describing what BITS action is being processed. This is
+//! used mostly for logging and error reporting reasons.
+//! The values of `Action` describe actions that could be in progress for
+//! BitsService or BitsRequest. When specifying a type, `ServiceAction` or
+//! `RequestAction`, can be used to restrict the action type to one of the two
+//! categories.
+//! A value of type `ServiceAction` or `RequestAction` can easily be converted
+//! to an `Action` using the `into()` method.
+
+use std::convert::From;
+use xpcom::interfaces::nsIBits;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum Action {
+ StartDownload,
+ MonitorDownload,
+ Complete,
+ Cancel,
+ SetMonitorInterval,
+ SetPriority,
+ SetNoProgressTimeout,
+ Resume,
+ Suspend,
+}
+
+impl Action {
+ pub fn description(&self) -> &'static str {
+ match self {
+ Action::StartDownload => "starting download",
+ Action::MonitorDownload => "monitoring download",
+ Action::Complete => "completing download",
+ Action::Cancel => "cancelling download",
+ Action::SetMonitorInterval => "changing monitor interval",
+ Action::SetPriority => "setting download priority",
+ Action::SetNoProgressTimeout => "setting no progress timeout",
+ Action::Resume => "resuming download",
+ Action::Suspend => "suspending download",
+ }
+ }
+
+ pub fn as_error_code(&self) -> i32 {
+ match self {
+ Action::StartDownload => nsIBits::ERROR_ACTION_START_DOWNLOAD,
+ Action::MonitorDownload => nsIBits::ERROR_ACTION_MONITOR_DOWNLOAD,
+ Action::Complete => nsIBits::ERROR_ACTION_COMPLETE,
+ Action::Cancel => nsIBits::ERROR_ACTION_CANCEL,
+ Action::SetMonitorInterval => nsIBits::ERROR_ACTION_CHANGE_MONITOR_INTERVAL,
+ Action::SetPriority => nsIBits::ERROR_ACTION_SET_PRIORITY,
+ Action::SetNoProgressTimeout => nsIBits::ERROR_ACTION_SET_NO_PROGRESS_TIMEOUT,
+ Action::Resume => nsIBits::ERROR_ACTION_RESUME,
+ Action::Suspend => nsIBits::ERROR_ACTION_SUSPEND,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum ServiceAction {
+ StartDownload,
+ MonitorDownload,
+}
+
+impl From<ServiceAction> for Action {
+ fn from(action: ServiceAction) -> Action {
+ match action {
+ ServiceAction::StartDownload => Action::StartDownload,
+ ServiceAction::MonitorDownload => Action::MonitorDownload,
+ }
+ }
+}
+
+impl ServiceAction {
+ pub fn as_error_code(&self) -> i32 {
+ Action::as_error_code(&(self.clone()).into())
+ }
+
+ pub fn description(&self) -> &'static str {
+ Action::description(&(self.clone()).into())
+ }
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum RequestAction {
+ Complete,
+ Cancel,
+ SetMonitorInterval,
+ SetPriority,
+ SetNoProgressTimeout,
+ Resume,
+ Suspend,
+}
+
+impl From<RequestAction> for Action {
+ fn from(action: RequestAction) -> Action {
+ match action {
+ RequestAction::Complete => Action::Complete,
+ RequestAction::Cancel => Action::Cancel,
+ RequestAction::SetMonitorInterval => Action::SetMonitorInterval,
+ RequestAction::SetPriority => Action::SetPriority,
+ RequestAction::SetNoProgressTimeout => Action::SetNoProgressTimeout,
+ RequestAction::Resume => Action::Resume,
+ RequestAction::Suspend => Action::Suspend,
+ }
+ }
+}
+
+impl RequestAction {
+ pub fn as_error_code(&self) -> i32 {
+ Action::as_error_code(&(self.clone()).into())
+ }
+
+ pub fn description(&self) -> &'static str {
+ Action::description(&(self.clone()).into())
+ }
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/dispatch_callback.rs b/toolkit/components/bitsdownload/src/bits_interface/dispatch_callback.rs
new file mode 100644
index 0000000000..b42637a993
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/dispatch_callback.rs
@@ -0,0 +1,199 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+use super::{
+ error::{BitsTaskError, ErrorCode, ErrorType},
+ BitsRequest,
+};
+use log::{error, info, warn};
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_OK};
+use xpcom::{
+ interfaces::{nsIBitsCallback, nsIBitsNewRequestCallback},
+ RefPtr,
+};
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum IsCallbackExpected {
+ CallbackExpected,
+ CallbackOptional,
+}
+pub use self::IsCallbackExpected::{CallbackExpected, CallbackOptional};
+
+// This is meant to be called at the end of a nsIBits Task. It attempts to
+// return the result via the callback given. If the callback is unavailable, a
+// log message will be printed indicating the results and (possibly) warning
+// than an expected callback was missing.
+pub fn maybe_dispatch_request_via_callback(
+ result: Result<RefPtr<BitsRequest>, BitsTaskError>,
+ maybe_callback: Result<&nsIBitsNewRequestCallback, BitsTaskError>,
+ expected: IsCallbackExpected,
+) -> Result<(), nsresult> {
+ if let Err(error) = maybe_callback.as_ref() {
+ if expected == CallbackExpected || error.error_type == ErrorType::CallbackOnWrongThread {
+ error!(
+ "Unexpected error when {} - No callback: {:?}",
+ error.error_action.description(),
+ error,
+ );
+ }
+ }
+ match result {
+ Ok(request) => match (maybe_callback, expected) {
+ (Ok(callback), _) => unsafe { callback.Success(request.coerce()) },
+ (Err(error), CallbackExpected) => {
+ error!(
+ "Success {} but there is no callback to return the result with",
+ error.error_action.description(),
+ );
+ NS_ERROR_FAILURE
+ }
+ (Err(error), CallbackOptional) => {
+ info!("Success {}", error.error_action.description());
+ NS_OK
+ }
+ },
+ Err(error) => match (maybe_callback, expected) {
+ (Ok(callback), _) => match error.error_code {
+ ErrorCode::None => unsafe {
+ callback.Failure(
+ error.error_type.bits_code(),
+ error.error_action.as_error_code(),
+ error.error_stage.bits_code(),
+ )
+ },
+ ErrorCode::Hresult(error_code) => unsafe {
+ callback.FailureHresult(
+ error.error_type.bits_code(),
+ error.error_action.as_error_code(),
+ error.error_stage.bits_code(),
+ error_code,
+ )
+ },
+ ErrorCode::Nsresult(error_code) => unsafe {
+ callback.FailureNsresult(
+ error.error_type.bits_code(),
+ error.error_action.as_error_code(),
+ error.error_stage.bits_code(),
+ error_code,
+ )
+ },
+ ErrorCode::Message(message) => unsafe {
+ callback.FailureString(
+ error.error_type.bits_code(),
+ error.error_action.as_error_code(),
+ error.error_stage.bits_code(),
+ &*message,
+ )
+ },
+ },
+ (Err(_), CallbackExpected) => {
+ error!("Error {}: {:?}", error.error_action.description(), error);
+ NS_ERROR_FAILURE
+ }
+ (Err(_), CallbackOptional) => {
+ warn!("Error {}: {:?}", error.error_action.description(), error);
+ NS_ERROR_FAILURE
+ }
+ },
+ }
+ .to_result()
+}
+
+// Intended to be used by an nsIBits XPCOM wrapper to return errors that occur
+// before dispatching a task off-thread. No return value is returned because it
+// will represent the return value of the callback function, which should not be
+// propagated.
+pub fn dispatch_pretask_interface_error(
+ error: BitsTaskError,
+ callback: &nsIBitsNewRequestCallback,
+) {
+ let _ = maybe_dispatch_request_via_callback(Err(error), Ok(callback), CallbackExpected);
+}
+
+// This is meant to be called at the end of a nsIBitsRequest Task. It attempts
+// to return the result via the callback given. If the callback is unavailable,
+// a log message will be printed indicating the results and (possibly) warning
+// than an expected callback was missing.
+pub fn maybe_dispatch_via_callback(
+ result: Result<(), BitsTaskError>,
+ maybe_callback: Result<&nsIBitsCallback, BitsTaskError>,
+ expected: IsCallbackExpected,
+) -> Result<(), nsresult> {
+ if let Err(error) = maybe_callback.as_ref() {
+ if expected == CallbackExpected || error.error_type == ErrorType::CallbackOnWrongThread {
+ error!(
+ "Unexpected error when {} - No callback: {:?}",
+ error.error_action.description(),
+ error,
+ );
+ }
+ }
+ match result {
+ Ok(()) => match (maybe_callback, expected) {
+ (Ok(callback), _) => unsafe { callback.Success() },
+ (Err(error), CallbackExpected) => {
+ error!(
+ "Success {} but there is no callback to return the result with",
+ error.error_action.description(),
+ );
+ NS_ERROR_FAILURE
+ }
+ (Err(error), CallbackOptional) => {
+ info!("Success {}", error.error_action.description());
+ NS_OK
+ }
+ },
+ Err(error) => match (maybe_callback, expected) {
+ (Ok(callback), _) => match error.error_code {
+ ErrorCode::None => unsafe {
+ callback.Failure(
+ error.error_type.bits_code(),
+ error.error_action.as_error_code(),
+ error.error_stage.bits_code(),
+ )
+ },
+ ErrorCode::Hresult(error_code) => unsafe {
+ callback.FailureHresult(
+ error.error_type.bits_code(),
+ error.error_action.as_error_code(),
+ error.error_stage.bits_code(),
+ error_code,
+ )
+ },
+ ErrorCode::Nsresult(error_code) => unsafe {
+ callback.FailureNsresult(
+ error.error_type.bits_code(),
+ error.error_action.as_error_code(),
+ error.error_stage.bits_code(),
+ error_code,
+ )
+ },
+ ErrorCode::Message(message) => unsafe {
+ callback.FailureString(
+ error.error_type.bits_code(),
+ error.error_action.as_error_code(),
+ error.error_stage.bits_code(),
+ &*message,
+ )
+ },
+ },
+ (Err(_), CallbackExpected) => {
+ error!("Error {}: {:?}", error.error_action.description(), error);
+ NS_ERROR_FAILURE
+ }
+ (Err(_), CallbackOptional) => {
+ warn!("Error {}: {:?}", error.error_action.description(), error);
+ NS_ERROR_FAILURE
+ }
+ },
+ }
+ .to_result()
+}
+
+// Intended to be used by an nsIBitsRequest XPCOM wrapper to return errors that
+// occur before dispatching a task off-thread. No return value is returned
+// because it will represent the return value of the callback function, which
+// should not be propagated.
+pub fn dispatch_pretask_request_error(error: BitsTaskError, callback: &nsIBitsCallback) {
+ let _ = maybe_dispatch_via_callback(Err(error), Ok(callback), CallbackExpected);
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/error.rs b/toolkit/components/bitsdownload/src/bits_interface/error.rs
new file mode 100644
index 0000000000..c046ae5e70
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/error.rs
@@ -0,0 +1,686 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+use super::action::Action;
+use bits_client::{
+ bits_protocol::{
+ CancelJobFailure, CompleteJobFailure, HResultMessage, MonitorJobFailure, ResumeJobFailure,
+ SetJobPriorityFailure, SetNoProgressTimeoutFailure, SetUpdateIntervalFailure,
+ StartJobFailure, SuspendJobFailure,
+ },
+ PipeError,
+};
+use comedy::error::HResult as ComedyError;
+use nserror::{nsresult, NS_ERROR_FAILURE};
+use nsstring::nsCString;
+use std::convert::From;
+use xpcom::interfaces::nsIBits;
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum ErrorType {
+ NullArgument,
+ InvalidArgument,
+ NotInitialized,
+ NoUtf8Conversion,
+ InvalidGuid,
+ PipeNotConnected,
+ PipeTimeout,
+ PipeBadWriteCount,
+ PipeApiError,
+ FailedToCreateBitsJob,
+ FailedToAddFileToJob,
+ FailedToApplyBitsJobSettings,
+ FailedToResumeBitsJob,
+ OtherBitsError,
+ OtherBitsClientError,
+ BitsJobNotFound,
+ FailedToGetBitsJob,
+ FailedToSuspendBitsJob,
+ FailedToCompleteBitsJob,
+ PartiallyCompletedBitsJob,
+ FailedToCancelBitsJob,
+ MissingResultData,
+ MissingCallback,
+ CallbackOnWrongThread,
+ MissingBitsService,
+ BitsServiceOnWrongThread,
+ MissingBitsRequest,
+ BitsRequestOnWrongThread,
+ MissingObserver,
+ ObserverOnWrongThread,
+ MissingContext,
+ ContextOnWrongThread,
+ FailedToStartThread,
+ FailedToConstructTaskRunnable,
+ FailedToDispatchRunnable,
+ TransferAlreadyComplete,
+ OperationAlreadyInProgress,
+ MissingBitsClient,
+ FailedToGetJobStatus,
+ BitsStateError,
+ BitsStateTransientError,
+ BitsStateCancelled,
+ BitsStateUnexpected,
+ FailedToConnectToBcm,
+}
+
+impl ErrorType {
+ pub fn bits_code(&self) -> i32 {
+ match self {
+ ErrorType::NullArgument => nsIBits::ERROR_TYPE_NULL_ARGUMENT,
+ ErrorType::InvalidArgument => nsIBits::ERROR_TYPE_INVALID_ARGUMENT,
+ ErrorType::NotInitialized => nsIBits::ERROR_TYPE_NOT_INITIALIZED,
+ ErrorType::NoUtf8Conversion => nsIBits::ERROR_TYPE_NO_UTF8_CONVERSION,
+ ErrorType::InvalidGuid => nsIBits::ERROR_TYPE_INVALID_GUID,
+ ErrorType::PipeNotConnected => nsIBits::ERROR_TYPE_PIPE_NOT_CONNECTED,
+ ErrorType::PipeTimeout => nsIBits::ERROR_TYPE_PIPE_TIMEOUT,
+ ErrorType::PipeBadWriteCount => nsIBits::ERROR_TYPE_PIPE_BAD_WRITE_COUNT,
+ ErrorType::PipeApiError => nsIBits::ERROR_TYPE_PIPE_API_ERROR,
+ ErrorType::FailedToCreateBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_CREATE_BITS_JOB,
+ ErrorType::FailedToAddFileToJob => nsIBits::ERROR_TYPE_FAILED_TO_ADD_FILE_TO_JOB,
+ ErrorType::FailedToApplyBitsJobSettings => {
+ nsIBits::ERROR_TYPE_FAILED_TO_APPLY_BITS_JOB_SETTINGS
+ }
+ ErrorType::FailedToResumeBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_RESUME_BITS_JOB,
+ ErrorType::OtherBitsError => nsIBits::ERROR_TYPE_OTHER_BITS_ERROR,
+ ErrorType::OtherBitsClientError => nsIBits::ERROR_TYPE_OTHER_BITS_CLIENT_ERROR,
+ ErrorType::BitsJobNotFound => nsIBits::ERROR_TYPE_BITS_JOB_NOT_FOUND,
+ ErrorType::FailedToGetBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_GET_BITS_JOB,
+ ErrorType::FailedToSuspendBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_SUSPEND_BITS_JOB,
+ ErrorType::FailedToCompleteBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_COMPLETE_BITS_JOB,
+ ErrorType::PartiallyCompletedBitsJob => {
+ nsIBits::ERROR_TYPE_PARTIALLY_COMPLETED_BITS_JOB
+ }
+ ErrorType::FailedToCancelBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_CANCEL_BITS_JOB,
+ ErrorType::MissingResultData => nsIBits::ERROR_TYPE_MISSING_RESULT_DATA,
+ ErrorType::MissingCallback => nsIBits::ERROR_TYPE_MISSING_CALLBACK,
+ ErrorType::CallbackOnWrongThread => nsIBits::ERROR_TYPE_CALLBACK_ON_WRONG_THREAD,
+ ErrorType::MissingBitsService => nsIBits::ERROR_TYPE_MISSING_BITS_SERVICE,
+ ErrorType::BitsServiceOnWrongThread => nsIBits::ERROR_TYPE_BITS_SERVICE_ON_WRONG_THREAD,
+ ErrorType::MissingBitsRequest => nsIBits::ERROR_TYPE_MISSING_BITS_REQUEST,
+ ErrorType::BitsRequestOnWrongThread => nsIBits::ERROR_TYPE_BITS_REQUEST_ON_WRONG_THREAD,
+ ErrorType::MissingObserver => nsIBits::ERROR_TYPE_MISSING_OBSERVER,
+ ErrorType::ObserverOnWrongThread => nsIBits::ERROR_TYPE_OBSERVER_ON_WRONG_THREAD,
+ ErrorType::MissingContext => nsIBits::ERROR_TYPE_MISSING_CONTEXT,
+ ErrorType::ContextOnWrongThread => nsIBits::ERROR_TYPE_CONTEXT_ON_WRONG_THREAD,
+ ErrorType::FailedToStartThread => nsIBits::ERROR_TYPE_FAILED_TO_START_THREAD,
+ ErrorType::FailedToConstructTaskRunnable => {
+ nsIBits::ERROR_TYPE_FAILED_TO_CONSTRUCT_TASK_RUNNABLE
+ }
+ ErrorType::FailedToDispatchRunnable => nsIBits::ERROR_TYPE_FAILED_TO_DISPATCH_RUNNABLE,
+ ErrorType::TransferAlreadyComplete => nsIBits::ERROR_TYPE_TRANSFER_ALREADY_COMPLETE,
+ ErrorType::OperationAlreadyInProgress => {
+ nsIBits::ERROR_TYPE_OPERATION_ALREADY_IN_PROGRESS
+ }
+ ErrorType::MissingBitsClient => nsIBits::ERROR_TYPE_MISSING_BITS_CLIENT,
+ ErrorType::FailedToGetJobStatus => nsIBits::ERROR_TYPE_FAILED_TO_GET_JOB_STATUS,
+ ErrorType::BitsStateError => nsIBits::ERROR_TYPE_BITS_STATE_ERROR,
+ ErrorType::BitsStateTransientError => nsIBits::ERROR_TYPE_BITS_STATE_TRANSIENT_ERROR,
+ ErrorType::BitsStateCancelled => nsIBits::ERROR_TYPE_BITS_STATE_CANCELLED,
+ ErrorType::BitsStateUnexpected => nsIBits::ERROR_TYPE_BITS_STATE_UNEXPECTED,
+ ErrorType::FailedToConnectToBcm => nsIBits::ERROR_TYPE_FAILED_TO_CONNECT_TO_BCM,
+ }
+ }
+}
+
+impl From<&PipeError> for ErrorType {
+ fn from(error: &PipeError) -> Self {
+ match error {
+ PipeError::NotConnected => ErrorType::PipeNotConnected,
+ PipeError::Timeout => ErrorType::PipeTimeout,
+ PipeError::WriteCount(_, _) => ErrorType::PipeBadWriteCount,
+ PipeError::Api(_) => ErrorType::PipeApiError,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum ErrorStage {
+ Pretask,
+ CommandThread,
+ AgentCommunication,
+ BitsClient,
+ MainThread,
+}
+
+impl ErrorStage {
+ pub fn bits_code(&self) -> i32 {
+ let val = match self {
+ ErrorStage::Pretask => nsIBits::ERROR_STAGE_PRETASK,
+ ErrorStage::CommandThread => nsIBits::ERROR_STAGE_COMMAND_THREAD,
+ ErrorStage::AgentCommunication => nsIBits::ERROR_STAGE_AGENT_COMMUNICATION,
+ ErrorStage::BitsClient => nsIBits::ERROR_STAGE_BITS_CLIENT,
+ ErrorStage::MainThread => nsIBits::ERROR_STAGE_MAIN_THREAD,
+ };
+ val as i32
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum ErrorCode {
+ None,
+ Hresult(i32),
+ Nsresult(nsresult),
+ Message(nsCString),
+}
+
+impl From<ComedyError> for ErrorCode {
+ fn from(error: ComedyError) -> Self {
+ ErrorCode::Hresult(error.code())
+ }
+}
+
+impl From<HResultMessage> for ErrorCode {
+ fn from(result: HResultMessage) -> Self {
+ ErrorCode::Hresult(result.hr)
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct BitsTaskError {
+ pub error_type: ErrorType,
+ pub error_action: Action,
+ pub error_stage: ErrorStage,
+ pub error_code: ErrorCode,
+}
+
+impl BitsTaskError {
+ pub fn new(
+ error_type: ErrorType,
+ error_action: Action,
+ error_stage: ErrorStage,
+ ) -> BitsTaskError {
+ BitsTaskError {
+ error_type,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::None,
+ }
+ }
+
+ pub fn missing_result(error_action: Action) -> BitsTaskError {
+ BitsTaskError {
+ error_type: ErrorType::MissingResultData,
+ error_action,
+ error_stage: ErrorStage::MainThread,
+ error_code: ErrorCode::None,
+ }
+ }
+
+ pub fn from_nsresult(
+ error_type: ErrorType,
+ error_action: Action,
+ error_stage: ErrorStage,
+ error_code: nsresult,
+ ) -> BitsTaskError {
+ BitsTaskError {
+ error_type,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Nsresult(error_code),
+ }
+ }
+
+ pub fn from_hresult(
+ error_type: ErrorType,
+ error_action: Action,
+ error_stage: ErrorStage,
+ error_code: i32,
+ ) -> BitsTaskError {
+ BitsTaskError {
+ error_type,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Hresult(error_code),
+ }
+ }
+
+ pub fn from_comedy(
+ error_type: ErrorType,
+ error_action: Action,
+ error_stage: ErrorStage,
+ comedy_error: ComedyError,
+ ) -> BitsTaskError {
+ BitsTaskError {
+ error_type,
+ error_action,
+ error_stage,
+ error_code: comedy_error.into(),
+ }
+ }
+
+ pub fn from_pipe(error_action: Action, pipe_error: PipeError) -> BitsTaskError {
+ let error_type = (&pipe_error).into();
+ match pipe_error {
+ PipeError::Api(comedy_error) => BitsTaskError {
+ error_type,
+ error_action,
+ error_stage: ErrorStage::AgentCommunication,
+ error_code: comedy_error.into(),
+ },
+ _ => BitsTaskError {
+ error_type,
+ error_action,
+ error_stage: ErrorStage::AgentCommunication,
+ error_code: ErrorCode::None,
+ },
+ }
+ }
+}
+
+impl From<BitsTaskError> for nsresult {
+ fn from(error: BitsTaskError) -> Self {
+ if let ErrorCode::Nsresult(rv) = error.error_code {
+ rv
+ } else {
+ NS_ERROR_FAILURE
+ }
+ }
+}
+
+impl From<StartJobFailure> for BitsTaskError {
+ fn from(error: StartJobFailure) -> Self {
+ let error_stage = ErrorStage::BitsClient;
+ let error_action = Action::StartDownload;
+ match error {
+ StartJobFailure::ArgumentValidation(message) => BitsTaskError {
+ error_type: ErrorType::InvalidArgument,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ StartJobFailure::Create(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToCreateBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ StartJobFailure::AddFile(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToAddFileToJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ StartJobFailure::ApplySettings(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToApplyBitsJobSettings,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ StartJobFailure::Resume(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToResumeBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ StartJobFailure::ConnectBcm(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToConnectToBcm,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ StartJobFailure::OtherBITS(error_code) => BitsTaskError {
+ error_type: ErrorType::OtherBitsError,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ StartJobFailure::Other(message) => BitsTaskError {
+ error_type: ErrorType::OtherBitsClientError,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ }
+ }
+}
+
+impl From<MonitorJobFailure> for BitsTaskError {
+ fn from(error: MonitorJobFailure) -> Self {
+ let error_stage = ErrorStage::BitsClient;
+ let error_action = Action::MonitorDownload;
+ match error {
+ MonitorJobFailure::ArgumentValidation(message) => BitsTaskError {
+ error_type: ErrorType::InvalidArgument,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ MonitorJobFailure::NotFound => BitsTaskError {
+ error_type: ErrorType::BitsJobNotFound,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::None,
+ },
+ MonitorJobFailure::GetJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToGetBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ MonitorJobFailure::ConnectBcm(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToConnectToBcm,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ MonitorJobFailure::OtherBITS(error_code) => BitsTaskError {
+ error_type: ErrorType::OtherBitsError,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ MonitorJobFailure::Other(message) => BitsTaskError {
+ error_type: ErrorType::OtherBitsClientError,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ }
+ }
+}
+
+impl From<SuspendJobFailure> for BitsTaskError {
+ fn from(error: SuspendJobFailure) -> Self {
+ let error_stage = ErrorStage::BitsClient;
+ let error_action = Action::Suspend;
+ match error {
+ SuspendJobFailure::NotFound => BitsTaskError {
+ error_type: ErrorType::BitsJobNotFound,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::None,
+ },
+ SuspendJobFailure::GetJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToGetBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ SuspendJobFailure::SuspendJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToSuspendBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ SuspendJobFailure::ConnectBcm(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToConnectToBcm,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ SuspendJobFailure::OtherBITS(error_code) => BitsTaskError {
+ error_type: ErrorType::OtherBitsError,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ SuspendJobFailure::Other(message) => BitsTaskError {
+ error_type: ErrorType::OtherBitsClientError,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ }
+ }
+}
+
+impl From<ResumeJobFailure> for BitsTaskError {
+ fn from(error: ResumeJobFailure) -> Self {
+ let error_stage = ErrorStage::BitsClient;
+ let error_action = Action::Resume;
+ match error {
+ ResumeJobFailure::NotFound => BitsTaskError {
+ error_type: ErrorType::BitsJobNotFound,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::None,
+ },
+ ResumeJobFailure::GetJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToGetBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ ResumeJobFailure::ResumeJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToResumeBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ ResumeJobFailure::ConnectBcm(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToConnectToBcm,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ ResumeJobFailure::OtherBITS(error_code) => BitsTaskError {
+ error_type: ErrorType::OtherBitsError,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ ResumeJobFailure::Other(message) => BitsTaskError {
+ error_type: ErrorType::OtherBitsClientError,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ }
+ }
+}
+
+impl From<SetJobPriorityFailure> for BitsTaskError {
+ fn from(error: SetJobPriorityFailure) -> Self {
+ let error_stage = ErrorStage::BitsClient;
+ let error_action = Action::SetPriority;
+ match error {
+ SetJobPriorityFailure::NotFound => BitsTaskError {
+ error_type: ErrorType::BitsJobNotFound,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::None,
+ },
+ SetJobPriorityFailure::GetJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToGetBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ SetJobPriorityFailure::ApplySettings(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToApplyBitsJobSettings,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ SetJobPriorityFailure::ConnectBcm(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToConnectToBcm,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ SetJobPriorityFailure::OtherBITS(error_code) => BitsTaskError {
+ error_type: ErrorType::OtherBitsError,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ SetJobPriorityFailure::Other(message) => BitsTaskError {
+ error_type: ErrorType::OtherBitsClientError,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ }
+ }
+}
+
+impl From<SetNoProgressTimeoutFailure> for BitsTaskError {
+ fn from(error: SetNoProgressTimeoutFailure) -> Self {
+ use self::SetNoProgressTimeoutFailure::*;
+
+ let error_stage = ErrorStage::BitsClient;
+ let error_action = Action::SetNoProgressTimeout;
+ match error {
+ NotFound => BitsTaskError {
+ error_type: ErrorType::BitsJobNotFound,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::None,
+ },
+ GetJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToGetBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ ApplySettings(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToApplyBitsJobSettings,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ ConnectBcm(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToConnectToBcm,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ OtherBITS(error_code) => BitsTaskError {
+ error_type: ErrorType::OtherBitsError,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ Other(message) => BitsTaskError {
+ error_type: ErrorType::OtherBitsClientError,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ }
+ }
+}
+
+impl From<SetUpdateIntervalFailure> for BitsTaskError {
+ fn from(error: SetUpdateIntervalFailure) -> Self {
+ let error_stage = ErrorStage::BitsClient;
+ let error_action = Action::SetMonitorInterval;
+ match error {
+ SetUpdateIntervalFailure::ArgumentValidation(message) => BitsTaskError {
+ error_type: ErrorType::InvalidArgument,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ SetUpdateIntervalFailure::NotFound => BitsTaskError {
+ error_type: ErrorType::BitsJobNotFound,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::None,
+ },
+ SetUpdateIntervalFailure::Other(message) => BitsTaskError {
+ error_type: ErrorType::OtherBitsClientError,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ }
+ }
+}
+
+impl From<CompleteJobFailure> for BitsTaskError {
+ fn from(error: CompleteJobFailure) -> Self {
+ let error_stage = ErrorStage::BitsClient;
+ let error_action = Action::Complete;
+ match error {
+ CompleteJobFailure::NotFound => BitsTaskError {
+ error_type: ErrorType::BitsJobNotFound,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::None,
+ },
+ CompleteJobFailure::GetJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToGetBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ CompleteJobFailure::CompleteJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToCompleteBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ CompleteJobFailure::PartialComplete => BitsTaskError {
+ error_type: ErrorType::PartiallyCompletedBitsJob,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::None,
+ },
+ CompleteJobFailure::ConnectBcm(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToConnectToBcm,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ CompleteJobFailure::OtherBITS(error_code) => BitsTaskError {
+ error_type: ErrorType::OtherBitsError,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ CompleteJobFailure::Other(message) => BitsTaskError {
+ error_type: ErrorType::OtherBitsClientError,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ }
+ }
+}
+
+impl From<CancelJobFailure> for BitsTaskError {
+ fn from(error: CancelJobFailure) -> Self {
+ let error_stage = ErrorStage::BitsClient;
+ let error_action = Action::Cancel;
+ match error {
+ CancelJobFailure::NotFound => BitsTaskError {
+ error_type: ErrorType::BitsJobNotFound,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::None,
+ },
+ CancelJobFailure::GetJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToGetBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ CancelJobFailure::CancelJob(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToCancelBitsJob,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ CancelJobFailure::ConnectBcm(error_code) => BitsTaskError {
+ error_type: ErrorType::FailedToConnectToBcm,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ CancelJobFailure::OtherBITS(error_code) => BitsTaskError {
+ error_type: ErrorType::OtherBitsError,
+ error_action,
+ error_stage,
+ error_code: error_code.into(),
+ },
+ CancelJobFailure::Other(message) => BitsTaskError {
+ error_type: ErrorType::OtherBitsClientError,
+ error_action,
+ error_stage,
+ error_code: ErrorCode::Message(nsCString::from(message)),
+ },
+ }
+ }
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/mod.rs b/toolkit/components/bitsdownload/src/bits_interface/mod.rs
new file mode 100644
index 0000000000..93d2ddeadd
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/mod.rs
@@ -0,0 +1,329 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+mod action;
+mod dispatch_callback;
+mod error;
+mod monitor;
+mod request;
+mod string;
+mod task;
+mod xpcom_methods;
+
+pub use self::error::BitsTaskError;
+use self::{
+ action::Action,
+ error::{
+ ErrorStage::Pretask,
+ ErrorType::{
+ FailedToConstructTaskRunnable, FailedToDispatchRunnable, FailedToStartThread,
+ InvalidArgument, NotInitialized,
+ },
+ },
+ request::BitsRequest,
+ string::Guid_from_nsCString,
+ task::{ClientInitData, MonitorDownloadTask, StartDownloadTask},
+};
+use nsIBits_method; // From xpcom_method.rs
+
+use bits_client::BitsProxyUsage;
+use log::{info, warn};
+use moz_task::{create_thread, Task, TaskRunnable};
+use nserror::{nsresult, NS_ERROR_ALREADY_INITIALIZED, NS_OK};
+use nsstring::{nsACString, nsCString};
+use std::cell::Cell;
+use xpcom::{
+ interfaces::{
+ nsIBits, nsIBitsNewRequestCallback, nsIRequestObserver, nsISupports, nsIThread,
+ nsProxyUsage,
+ },
+ xpcom, xpcom_method, RefPtr,
+};
+
+#[no_mangle]
+pub unsafe extern "C" fn new_bits_service(result: *mut *const nsIBits) {
+ let service: RefPtr<BitsService> = BitsService::new();
+ RefPtr::new(service.coerce::<nsIBits>()).forget(&mut *result);
+}
+
+#[xpcom(implement(nsIBits), nonatomic)]
+pub struct BitsService {
+ // This command thread will be used to send commands (ex: Suspend, Resume)
+ // to a running job. It will be started up when the first job is created and
+ // shutdown when all jobs have been completed or cancelled.
+ command_thread: Cell<Option<RefPtr<nsIThread>>>,
+ client_init_data: Cell<Option<ClientInitData>>,
+ // This count will track the number of in-progress requests so that the
+ // service knows when the command_thread is no longer being used and can be
+ // shut down.
+ // `BitsRequest::new()` will increment this and it will be decremented
+ // either when cancel/complete is called, or when the request is dropped
+ // (if it didn't decrement it already).
+ // The count will also be incremented when an action to create a request
+ // starts and decremented when the action ends and returns the result via
+ // the callback. This prevents the command thread from being shut down while
+ // a job is being created.
+ request_count: Cell<u32>,
+}
+
+/// This implements the nsIBits interface, documented in nsIBits.idl, to enable
+/// BITS job management. Specifically, this interface can start a download or
+/// connect to an existing download. Doing so will create a BitsRequest through
+/// which the transfer can be further manipulated.
+///
+/// This is a primarily asynchronous interface, which is accomplished via
+/// callbacks of type nsIBitsNewRequestCallback. The callback is passed in as
+/// an argument and is then passed off-thread via a Task. The Task interacts
+/// with BITS and is dispatched back to the main thread with the BITS result.
+/// Back on the main thread, it returns that result via the callback including,
+/// if successful, a BitsRequest.
+impl BitsService {
+ pub fn new() -> RefPtr<BitsService> {
+ BitsService::allocate(InitBitsService {
+ command_thread: Cell::new(None),
+ client_init_data: Cell::new(None),
+ request_count: Cell::new(0),
+ })
+ }
+
+ fn get_client_init(&self) -> Option<ClientInitData> {
+ let maybe_init_data = self.client_init_data.take();
+ self.client_init_data.set(maybe_init_data.clone());
+ maybe_init_data
+ }
+
+ // Returns the handle to the command thread. If it has not been started yet,
+ // the thread will be started.
+ fn get_command_thread(&self) -> Result<RefPtr<nsIThread>, nsresult> {
+ let mut command_thread = self.command_thread.take();
+ if command_thread.is_none() {
+ command_thread.replace(create_thread("BitsCommander")?);
+ }
+ self.command_thread.set(command_thread.clone());
+ Ok(command_thread.unwrap())
+ }
+
+ // Asynchronously shuts down the command thread. The thread is not shutdown
+ // until the event queue is empty, so any tasks that were dispatched before
+ // this is called will still run.
+ // Leaves None in self.command_thread
+ fn shutdown_command_thread(&self) {
+ if let Some(command_thread) = self.command_thread.take() {
+ if let Err(rv) = unsafe { command_thread.AsyncShutdown() }.to_result() {
+ warn!("Failed to shut down command thread: {}", rv);
+ warn!("Releasing reference to thread that failed to shut down!");
+ }
+ }
+ }
+
+ fn dispatch_runnable_to_command_thread(
+ &self,
+ task: Box<dyn Task + Send + Sync>,
+ task_runnable_name: &'static str,
+ action: Action,
+ ) -> Result<(), BitsTaskError> {
+ let command_thread = self
+ .get_command_thread()
+ .map_err(|rv| BitsTaskError::from_nsresult(FailedToStartThread, action, Pretask, rv))?;
+ let runnable = TaskRunnable::new(task_runnable_name, task).map_err(|rv| {
+ BitsTaskError::from_nsresult(FailedToConstructTaskRunnable, action, Pretask, rv)
+ })?;
+ TaskRunnable::dispatch(runnable, &command_thread).map_err(|rv| {
+ BitsTaskError::from_nsresult(FailedToDispatchRunnable, action, Pretask, rv)
+ })
+ }
+
+ fn inc_request_count(&self) {
+ self.request_count.set(self.request_count.get() + 1);
+ }
+
+ fn dec_request_count(&self) {
+ let mut count = self.request_count.get();
+ if count == 0 {
+ warn!("Attempted to decrement request count, but it is 0");
+ return;
+ }
+ count -= 1;
+ self.request_count.set(count);
+
+ if count == 0 {
+ self.shutdown_command_thread();
+ }
+ }
+
+ xpcom_method!(
+ get_initialized => GetInitialized() -> bool
+ );
+ fn get_initialized(&self) -> Result<bool, nsresult> {
+ Ok(self.get_client_init().is_some())
+ }
+
+ xpcom_method!(
+ init => Init(
+ job_name: *const nsACString,
+ save_path_prefix: *const nsACString,
+ monitor_timeout_ms: u32
+ )
+ );
+ fn init(
+ &self,
+ job_name: &nsACString,
+ save_path_prefix: &nsACString,
+ monitor_timeout_ms: u32,
+ ) -> Result<(), nsresult> {
+ let previous_data = self.client_init_data.take();
+ if previous_data.is_some() {
+ self.client_init_data.set(previous_data);
+ return Err(NS_ERROR_ALREADY_INITIALIZED);
+ }
+
+ info!(
+ "BitsService initialized with job_name: {}, save_path_prefix: {}, timeout: {}",
+ job_name, save_path_prefix, monitor_timeout_ms,
+ );
+
+ self.client_init_data.set(Some(ClientInitData::new(
+ nsCString::from(job_name),
+ nsCString::from(save_path_prefix),
+ monitor_timeout_ms,
+ )));
+
+ Ok(())
+ }
+
+ nsIBits_method!(
+ [Action::StartDownload]
+ start_download => StartDownload(
+ download_url: *const nsACString,
+ save_rel_path: *const nsACString,
+ proxy: nsProxyUsage,
+ no_progress_timeout_secs: u32,
+ update_interval_ms: u32,
+ observer: *const nsIRequestObserver,
+ [optional] context: *const nsISupports,
+ )
+ );
+ fn start_download(
+ &self,
+ download_url: &nsACString,
+ save_rel_path: &nsACString,
+ proxy: nsProxyUsage,
+ no_progress_timeout_secs: u32,
+ update_interval_ms: u32,
+ observer: &nsIRequestObserver,
+ context: Option<&nsISupports>,
+ callback: &nsIBitsNewRequestCallback,
+ ) -> Result<(), BitsTaskError> {
+ let client_init_data = self
+ .get_client_init()
+ .ok_or_else(|| BitsTaskError::new(NotInitialized, Action::StartDownload, Pretask))?;
+ if update_interval_ms >= client_init_data.monitor_timeout_ms {
+ return Err(BitsTaskError::new(
+ InvalidArgument,
+ Action::StartDownload,
+ Pretask,
+ ));
+ }
+ let proxy = match proxy {
+ nsIBits::PROXY_NONE => BitsProxyUsage::NoProxy,
+ nsIBits::PROXY_PRECONFIG => BitsProxyUsage::Preconfig,
+ nsIBits::PROXY_AUTODETECT => BitsProxyUsage::AutoDetect,
+ _ => {
+ return Err(BitsTaskError::new(
+ InvalidArgument,
+ Action::StartDownload,
+ Pretask,
+ ));
+ }
+ };
+
+ let task: Box<StartDownloadTask> = Box::new(StartDownloadTask::new(
+ client_init_data,
+ nsCString::from(download_url),
+ nsCString::from(save_rel_path),
+ proxy,
+ no_progress_timeout_secs,
+ update_interval_ms,
+ RefPtr::new(self),
+ RefPtr::new(observer),
+ context.map(RefPtr::new),
+ RefPtr::new(callback),
+ ));
+
+ let dispatch_result = self.dispatch_runnable_to_command_thread(
+ task,
+ "BitsService::start_download",
+ Action::StartDownload,
+ );
+
+ if dispatch_result.is_ok() {
+ // Increment the request count when we dispatch an action to start
+ // a job, decrement it when the action completes. See the
+ // declaration of InitBitsService::request_count for details.
+ self.inc_request_count();
+ }
+
+ dispatch_result
+ }
+
+ nsIBits_method!(
+ [Action::MonitorDownload]
+ monitor_download => MonitorDownload(
+ id: *const nsACString,
+ update_interval_ms: u32,
+ observer: *const nsIRequestObserver,
+ [optional] context: *const nsISupports,
+ )
+ );
+ fn monitor_download(
+ &self,
+ id: &nsACString,
+ update_interval_ms: u32,
+ observer: &nsIRequestObserver,
+ context: Option<&nsISupports>,
+ callback: &nsIBitsNewRequestCallback,
+ ) -> Result<(), BitsTaskError> {
+ let client_init_data = self
+ .get_client_init()
+ .ok_or_else(|| BitsTaskError::new(NotInitialized, Action::MonitorDownload, Pretask))?;
+ if update_interval_ms >= client_init_data.monitor_timeout_ms {
+ return Err(BitsTaskError::new(
+ InvalidArgument,
+ Action::MonitorDownload,
+ Pretask,
+ ));
+ }
+ let guid = Guid_from_nsCString(&nsCString::from(id), Action::MonitorDownload, Pretask)?;
+
+ let task: Box<MonitorDownloadTask> = Box::new(MonitorDownloadTask::new(
+ client_init_data,
+ guid,
+ update_interval_ms,
+ RefPtr::new(self),
+ RefPtr::new(observer),
+ context.map(RefPtr::new),
+ RefPtr::new(callback),
+ ));
+
+ let dispatch_result = self.dispatch_runnable_to_command_thread(
+ task,
+ "BitsService::monitor_download",
+ Action::MonitorDownload,
+ );
+
+ if dispatch_result.is_ok() {
+ // Increment the request count when we dispatch an action to start
+ // a job, decrement it when the action completes. See the
+ // declaration of InitBitsService::request_count for details.
+ self.inc_request_count();
+ }
+
+ dispatch_result
+ }
+}
+
+impl Drop for BitsService {
+ fn drop(&mut self) {
+ self.shutdown_command_thread();
+ }
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/monitor.rs b/toolkit/components/bitsdownload/src/bits_interface/monitor.rs
new file mode 100644
index 0000000000..4332efe04b
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/monitor.rs
@@ -0,0 +1,247 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+use bits_interface::{error::ErrorType, BitsRequest};
+
+use bits_client::{
+ bits_protocol::HResultMessage, BitsJobState, BitsMonitorClient, Guid, JobStatus, PipeError,
+};
+use crossbeam_utils::atomic::AtomicCell;
+use log::error;
+use moz_task::{get_main_thread, is_main_thread};
+use nserror::{nsresult, NS_ERROR_ABORT, NS_ERROR_FAILURE, NS_OK};
+use nsstring::{nsACString, nsCString};
+use xpcom::{
+ interfaces::{nsIEventTarget, nsIThread},
+ xpcom, xpcom_method, RefPtr, ThreadBoundRefPtr,
+};
+
+/// This function takes the output of BitsMonitorClient::get_status() and uses
+/// it to determine whether the the transfer has started. If the argument
+/// contains an error, the transfer is considered started because we also
+/// consider a transfer stopped on error.
+/// This function is used to determine whether the OnStartRequest and OnProgress
+/// observer functions should be called.
+fn transfer_started(status: &Result<Result<JobStatus, HResultMessage>, PipeError>) -> bool {
+ match status.as_ref() {
+ Ok(Ok(job_status)) => match job_status.state {
+ BitsJobState::Queued | BitsJobState::Connecting => false,
+ _ => true,
+ },
+ Ok(Err(_)) => true,
+ Err(_) => true,
+ }
+}
+
+/// This function takes the output of BitsMonitorClient::get_status() and uses
+/// it to determine whether the the transfer has stopped. If the argument
+/// contains an error, the transfer is considered stopped.
+/// A number of things will be done when a transfer is completed, such as
+/// calling the observer's OnStopRequest method.
+fn transfer_completed(status: &Result<Result<JobStatus, HResultMessage>, PipeError>) -> bool {
+ match status.as_ref() {
+ Ok(Ok(job_status)) => match job_status.state {
+ BitsJobState::Error
+ | BitsJobState::Transferred
+ | BitsJobState::Acknowledged
+ | BitsJobState::Cancelled => true,
+ _ => false,
+ },
+ Ok(Err(_)) => true,
+ Err(_) => true,
+ }
+}
+
+/// BitsRequest implements nsIRequest, which means that it must be able to
+/// provide an nsresult status code. This function provides such a status code
+/// based on the output of BitsMonitorClient::get_status().
+fn status_to_nsresult(status: &Result<Result<JobStatus, HResultMessage>, PipeError>) -> nsresult {
+ match status.as_ref() {
+ Ok(Ok(job_status)) => match job_status.state {
+ BitsJobState::Cancelled => NS_ERROR_ABORT,
+ BitsJobState::Transferred | BitsJobState::Acknowledged => NS_OK,
+ _ => NS_ERROR_FAILURE,
+ },
+ Ok(Err(_)) => NS_ERROR_FAILURE,
+ Err(_) => NS_ERROR_FAILURE,
+ }
+}
+
+/// This function takes the output of BitsMonitorClient::get_status() and uses
+/// it to determine the result value of the request. This will take the form of
+/// an Optional ErrorType value with a None value indicating success.
+fn status_to_request_result(
+ status: &Result<Result<JobStatus, HResultMessage>, PipeError>,
+) -> Option<ErrorType> {
+ match status.as_ref() {
+ Ok(Ok(job_status)) => match job_status.state {
+ BitsJobState::Transferred | BitsJobState::Acknowledged => None,
+ BitsJobState::Cancelled => Some(ErrorType::BitsStateCancelled),
+ BitsJobState::Error => Some(ErrorType::BitsStateError),
+ BitsJobState::TransientError => Some(ErrorType::BitsStateTransientError),
+ _ => Some(ErrorType::BitsStateUnexpected),
+ },
+ Ok(Err(_)) => Some(ErrorType::FailedToGetJobStatus),
+ Err(pipe_error) => Some(pipe_error.into()),
+ }
+}
+
+/// MonitorRunnable is an nsIRunnable meant to be dispatched off thread. It will
+/// perform the following actions:
+/// 1. Call BitsMonitorClient::get_status and store the result.
+/// 2. Dispatch itself back to the main thread.
+/// 3. Report the status to the observer.
+/// 4. If the transfer has finished, free its data and return, otherwise:
+/// 5. Dispatch itself back to its original thread and repeat from step 1.
+#[xpcom(implement(nsIRunnable, nsINamed), atomic)]
+pub struct MonitorRunnable {
+ request: AtomicCell<Option<ThreadBoundRefPtr<BitsRequest>>>,
+ id: Guid,
+ timeout: u32,
+ monitor_client: AtomicCell<Option<BitsMonitorClient>>,
+ // This cell contains an Option, possibly containing the return value of
+ // BitsMonitorClient::get_status.
+ status: AtomicCell<Option<Result<Result<JobStatus, HResultMessage>, PipeError>>>,
+ request_started: AtomicCell<bool>,
+ in_error_state: AtomicCell<bool>,
+}
+
+impl MonitorRunnable {
+ pub fn new(
+ request: RefPtr<BitsRequest>,
+ id: Guid,
+ timeout: u32,
+ monitor_client: BitsMonitorClient,
+ ) -> RefPtr<MonitorRunnable> {
+ MonitorRunnable::allocate(InitMonitorRunnable {
+ request: AtomicCell::new(Some(ThreadBoundRefPtr::new(request))),
+ id,
+ timeout,
+ monitor_client: AtomicCell::new(Some(monitor_client)),
+ status: AtomicCell::new(None),
+ request_started: AtomicCell::new(false),
+ in_error_state: AtomicCell::new(false),
+ })
+ }
+
+ pub fn dispatch(&self, thread: RefPtr<nsIThread>) -> Result<(), nsresult> {
+ unsafe { thread.DispatchFromScript(self.coerce(), nsIEventTarget::DISPATCH_NORMAL) }
+ .to_result()
+ }
+
+ fn free_mainthread_data(&self) {
+ if is_main_thread() {
+ // This is not safe to free unless on the main thread
+ self.request.swap(None);
+ } else {
+ error!("Attempting to free data on the main thread, but not on the main thread");
+ }
+ }
+
+ xpcom_method!(run => Run());
+
+ /// This method is essentially a error-handling wrapper around try_run.
+ /// This is done to make it easier to ensure that main-thread data is freed
+ /// on the main thread.
+ pub fn run(&self) -> Result<(), nsresult> {
+ if self.in_error_state.load() {
+ self.free_mainthread_data();
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ self.try_run().or_else(|error_message| {
+ error!("{}", error_message);
+
+ // Once an error has been encountered, we need to free all of our
+ // data, but it all needs to be freed on the main thread.
+ self.in_error_state.store(true);
+ if is_main_thread() {
+ self.free_mainthread_data();
+ Err(NS_ERROR_FAILURE)
+ } else {
+ self.dispatch(get_main_thread()?)
+ }
+ })
+ }
+
+ /// This function performs all the primary functionality of MonitorRunnable.
+ /// See the documentation for InitMonitorRunnable/MonitorRunnable for
+ /// details.
+ pub fn try_run(&self) -> Result<(), String> {
+ if !is_main_thread() {
+ let mut monitor_client = self
+ .monitor_client
+ .swap(None)
+ .ok_or("Missing monitor client")?;
+ self.status
+ .store(Some(monitor_client.get_status(self.timeout)));
+ self.monitor_client.store(Some(monitor_client));
+
+ let main_thread =
+ get_main_thread().map_err(|rv| format!("Unable to get main thread: {}", rv))?;
+
+ self.dispatch(main_thread)
+ .map_err(|rv| format!("Unable to dispatch to main thread: {}", rv))
+ } else {
+ let status = self.status.swap(None).ok_or("Missing status object")?;
+ let tb_request = self.request.swap(None).ok_or("Missing request")?;
+
+ // This block bounds the scope for request to ensure that it ends
+ // before re-storing tb_request.
+ let maybe_next_thread: Option<RefPtr<nsIThread>> = {
+ let request = tb_request
+ .get_ref()
+ .ok_or("BitsRequest is on the wrong thread")?;
+
+ if !self.request_started.load() && transfer_started(&status) {
+ self.request_started.store(true);
+ request.on_start();
+ }
+
+ if self.request_started.load() {
+ if let Ok(Ok(job_status)) = status.as_ref() {
+ let transferred_bytes = job_status.progress.transferred_bytes as i64;
+ let total_bytes = match job_status.progress.total_bytes {
+ Some(total) => total as i64,
+ None => -1i64,
+ };
+ request.on_progress(transferred_bytes, total_bytes);
+ }
+ }
+
+ if transfer_completed(&status) {
+ request.on_stop(Some((
+ status_to_nsresult(&status),
+ status_to_request_result(&status),
+ )));
+
+ // Transfer completed. No need to dispatch back to the monitor thread.
+ None
+ } else {
+ Some(
+ request
+ .get_monitor_thread()
+ .ok_or("Missing monitor thread")?,
+ )
+ }
+ };
+
+ self.request.store(Some(tb_request));
+
+ match maybe_next_thread {
+ Some(next_thread) => self
+ .dispatch(next_thread)
+ .map_err(|rv| format!("Unable to dispatch to thread: {}", rv)),
+ None => {
+ self.free_mainthread_data();
+ Ok(())
+ }
+ }
+ }
+ }
+
+ xpcom_method!(get_name => GetName() -> nsACString);
+ fn get_name(&self) -> Result<nsCString, nsresult> {
+ Ok(nsCString::from("BitsRequest::Monitor"))
+ }
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/request.rs b/toolkit/components/bitsdownload/src/bits_interface/request.rs
new file mode 100644
index 0000000000..460e3b4950
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/request.rs
@@ -0,0 +1,763 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use super::{
+ action::{Action, ServiceAction},
+ error::{
+ ErrorStage::{MainThread, Pretask},
+ ErrorType,
+ ErrorType::{
+ BitsStateCancelled, FailedToDispatchRunnable, FailedToStartThread, InvalidArgument,
+ OperationAlreadyInProgress, TransferAlreadyComplete,
+ },
+ },
+ monitor::MonitorRunnable,
+ task::{
+ CancelTask, ChangeMonitorIntervalTask, CompleteTask, Priority, ResumeTask,
+ SetNoProgressTimeoutTask, SetPriorityTask, SuspendTask,
+ },
+ BitsService, BitsTaskError,
+};
+use nsIBitsRequest_method; // From xpcom_method.rs
+
+use bits_client::{BitsMonitorClient, Guid};
+use log::{error, info, warn};
+use moz_task::create_thread;
+use nserror::{nsresult, NS_ERROR_ABORT, NS_ERROR_NOT_IMPLEMENTED, NS_OK};
+use nsstring::{nsACString, nsCString};
+use std::{cell::Cell, fmt};
+use xpcom::{
+ interfaces::{
+ nsIBits, nsIBitsCallback, nsILoadGroup, nsIProgressEventSink, nsIRequestObserver,
+ nsISupports, nsIThread, nsLoadFlags,
+ },
+ xpcom, xpcom_method, RefPtr, XpCom,
+};
+
+/// This structure exists to resolve a race condition. If cancel is called, we
+/// don't want to immediately set the request state to cancelled, because the
+/// cancel action could fail. But it's possible that on_stop() could be called
+/// before the cancel action resolves, and the correct status should be sent to
+/// OnStopRequest.
+/// This is how this race condition will be resolved:
+/// 1. cancel() is called, which sets the CancelAction to InProgress and
+/// stores in it the status that should be set if it succeeds.
+/// 2. cancel() dispatches the cancel task off thread.
+/// At this point, things unfold in one of two ways, depending on the race
+/// condition. Either:
+/// 3. The cancel task returns to the main thread and calls
+/// BitsRequest::finish_cancel_action.
+/// 4. If the cancel action succeeded, the appropriate status codes are set
+/// and the CancelAction is set to RequestEndPending.
+/// If the cancel action failed, the CancelAction is set to NotInProgress.
+/// 5. The MonitorRunnable detects that the transfer has ended and calls
+/// BitsRequest::on_stop, passing different status codes.
+/// 6. BitsRequest::on_stop checks the CancelAction and
+/// If the cancel action succeeded and RequestEndPending is set, the
+/// status codes that were set by BitsRequest::finish_cancel_action are
+/// left untouched.
+/// If the cancel action failed and NotInProgress is set, the status codes
+/// passed to BitsRequest::on_stop are set.
+/// 7. onStopRequest is called with the correct status code.
+/// Or, if MonitorRunnable calls on_stop before the cancel task can finish:
+/// 3. The MonitorRunnable detects that the transfer has ended and calls
+/// BitsRequest::on_stop, passing status codes to it.
+/// 4. BitsRequest::on_stop checks the CancelAction, sees it is set to
+/// InProgress, and sets it to RequestEndedWhileInProgress, carrying over
+/// the status code from InProgress.
+/// 5. BitsRequest::on_stop sets the status to the value passed to it, which
+/// will be overwritten if the cancel action succeeds, but kept if it
+/// fails.
+/// 6. BitsRequest::on_stop returns early, without calling OnStopRequest.
+/// 7. The cancel task returns to the main thread and calls
+/// BitsRequest::finish_cancel_action.
+/// 8. If the cancel action succeeded, the status codes are set from the
+/// value stored in RequestEndedWhileInProgress.
+/// If the cancel action failed, the status codes are not changed.
+/// 9. The CancelAction is set to NotInProgress.
+/// 10. BitsRequest::finish_cancel_action calls BitsRequest::on_stop without
+/// passing it any status codes.
+/// 11. onStopRequest is called with the correct status code.
+#[derive(Clone, Copy, PartialEq)]
+enum CancelAction {
+ NotInProgress,
+ InProgress(Option<nsresult>),
+ RequestEndedWhileInProgress(Option<nsresult>),
+ RequestEndPending,
+}
+
+#[xpcom(implement(nsIBitsRequest), nonatomic)]
+pub struct BitsRequest {
+ bits_id: Guid,
+ bits_service: RefPtr<BitsService>,
+ // Stores the value to be returned by nsIRequest::IsPending.
+ download_pending: Cell<bool>,
+ // Stores the value to be returned by nsIRequest::GetStatus.
+ download_status_nsresult: Cell<nsresult>,
+ // Stores an ErrorType if the request has failed, or None to represent the
+ // success state.
+ download_status_error_type: Cell<Option<ErrorType>>,
+ // This option will be None only after OnStopRequest has been fired.
+ monitor_thread: Cell<Option<RefPtr<nsIThread>>>,
+ monitor_timeout_ms: u32,
+ observer: RefPtr<nsIRequestObserver>,
+ // started indicates whether or not OnStartRequest has been fired.
+ started: Cell<bool>,
+ // finished indicates whether or not we have called
+ // BitsService::dec_request_count() to (assuming that there are no other
+ // requests) shutdown the command thread.
+ finished: Cell<bool>,
+ cancel_action: Cell<CancelAction>,
+}
+
+impl fmt::Debug for BitsRequest {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "BitsRequest {{ id: {} }}", self.bits_id)
+ }
+}
+
+/// This implements the nsIBitsRequest interface, documented in nsIBits.idl, to
+/// enable BITS job management. This interface deals only with BITS jobs that
+/// already exist. Jobs can be created via BitsService, which will create a
+/// BitsRequest for that job.
+///
+/// This is a primarily asynchronous interface, which is accomplished via
+/// callbacks of type nsIBitsCallback. The callback is passed in as an argument
+/// and is then passed off-thread via a Task. The Task interacts with BITS and
+/// is dispatched back to the main thread with the BITS result. Back on the main
+/// thread, it returns that result via the callback.
+impl BitsRequest {
+ pub fn new(
+ id: Guid,
+ bits_service: RefPtr<BitsService>,
+ monitor_timeout_ms: u32,
+ observer: RefPtr<nsIRequestObserver>,
+ context: Option<RefPtr<nsISupports>>,
+ monitor_client: BitsMonitorClient,
+ action: ServiceAction,
+ ) -> Result<RefPtr<BitsRequest>, BitsTaskError> {
+ let _ = context;
+ let action: Action = action.into();
+ let monitor_thread = create_thread("BitsMonitor").map_err(|rv| {
+ BitsTaskError::from_nsresult(FailedToStartThread, action, MainThread, rv)
+ })?;
+
+ // BitsRequest.drop() will call dec_request_count
+ bits_service.inc_request_count();
+ let request: RefPtr<BitsRequest> = BitsRequest::allocate(InitBitsRequest {
+ bits_id: id.clone(),
+ bits_service,
+ download_pending: Cell::new(true),
+ download_status_nsresult: Cell::new(NS_OK),
+ download_status_error_type: Cell::new(None),
+ monitor_thread: Cell::new(Some(monitor_thread.clone())),
+ monitor_timeout_ms,
+ observer,
+ started: Cell::new(false),
+ finished: Cell::new(false),
+ cancel_action: Cell::new(CancelAction::NotInProgress),
+ });
+
+ let monitor_runnable =
+ MonitorRunnable::new(request.clone(), id, monitor_timeout_ms, monitor_client);
+
+ if let Err(rv) = monitor_runnable.dispatch(monitor_thread.clone()) {
+ request.shutdown_monitor_thread();
+ return Err(BitsTaskError::from_nsresult(
+ FailedToDispatchRunnable,
+ action,
+ MainThread,
+ rv,
+ ));
+ }
+
+ Ok(request)
+ }
+
+ pub fn get_monitor_thread(&self) -> Option<RefPtr<nsIThread>> {
+ let monitor_thread = self.monitor_thread.take();
+ self.monitor_thread.set(monitor_thread.clone());
+ monitor_thread
+ }
+
+ fn has_monitor_thread(&self) -> bool {
+ let maybe_monitor_thread = self.monitor_thread.take();
+ let transferred = maybe_monitor_thread.is_some();
+ self.monitor_thread.set(maybe_monitor_thread);
+ transferred
+ }
+
+ /// If this returns an true, it means that:
+ /// - The monitor thread and monitor runnable may have been shut down
+ /// - The BITS job is not in the TRANSFERRING state
+ /// - The download either completed, failed, or was cancelled
+ /// - The BITS job may or may not still need complete() or cancel() to be
+ /// called on it
+ fn request_has_transferred(&self) -> bool {
+ self.request_has_completed() || !self.has_monitor_thread()
+ }
+
+ /// If this returns an error, it means that:
+ /// - complete() or cancel() has been called on the BITS job.
+ /// - BitsService::dec_request_count has already been called.
+ /// - The BitsClient object that this request was using may have been
+ /// dropped.
+ fn request_has_completed(&self) -> bool {
+ self.finished.get()
+ }
+
+ fn shutdown_monitor_thread(&self) {
+ if let Some(monitor_thread) = self.monitor_thread.take() {
+ if let Err(rv) = unsafe { monitor_thread.AsyncShutdown() }.to_result() {
+ warn!("Failed to shut down monitor thread: {:?}", rv);
+ warn!("Releasing reference to thread that failed to shut down!");
+ }
+ }
+ }
+
+ /**
+ * To be called when the transfer starts. Fires observer.OnStartRequest exactly once.
+ */
+ pub fn on_start(&self) {
+ if self.started.get() {
+ return;
+ }
+ self.started.set(true);
+ if let Err(rv) = unsafe { self.observer.OnStartRequest(self.coerce()) }.to_result() {
+ // This behavior is specified by nsIRequestObserver.
+ // See nsIRequestObserver.idl
+ info!(
+ "Cancelling download because OnStartRequest rejected with: {:?}",
+ rv
+ );
+ if let Err(rv) = self.cancel(NS_ERROR_ABORT, None) {
+ warn!("Failed to cancel download: {:?}", rv);
+ }
+ }
+ }
+
+ pub fn on_progress(&self, transferred_bytes: i64, total_bytes: i64) {
+ if let Some(progress_event_sink) = self.observer.query_interface::<nsIProgressEventSink>() {
+ unsafe {
+ progress_event_sink.OnProgress(self.coerce(), transferred_bytes, total_bytes);
+ }
+ }
+ }
+
+ /// To be called when the transfer stops (fails or completes). Fires
+ /// observer.OnStopRequest exactly once, though the call may be delayed to
+ /// resolve a race condition.
+ ///
+ /// The status values, if passed, will be stored in download_status_nsresult
+ /// and download_status_error_type, unless they have been overridden by a
+ /// cancel action.
+ ///
+ /// See the documentation for CancelAction for details.
+ pub fn on_stop(&self, maybe_status: Option<(nsresult, Option<ErrorType>)>) {
+ if !self.has_monitor_thread() {
+ // If the request has already stopped, don't stop it again
+ return;
+ }
+
+ match self.cancel_action.get() {
+ CancelAction::InProgress(saved_status)
+ | CancelAction::RequestEndedWhileInProgress(saved_status) => {
+ if let Some((status, result)) = maybe_status {
+ self.download_status_nsresult.set(status);
+ self.download_status_error_type.set(result);
+ }
+
+ info!("Deferring OnStopRequest until Cancel Task completes");
+ self.cancel_action
+ .set(CancelAction::RequestEndedWhileInProgress(saved_status));
+ return;
+ }
+ CancelAction::NotInProgress => {
+ if let Some((status, result)) = maybe_status {
+ self.download_status_nsresult.set(status);
+ self.download_status_error_type.set(result);
+ }
+ }
+ CancelAction::RequestEndPending => {
+ // Don't set the status variables if the end of this request was
+ // the result of a cancel action. The cancel action already set
+ // those values and they should not be changed.
+ // See the CancelAction documentation for details.
+ }
+ }
+
+ self.download_pending.set(false);
+ self.shutdown_monitor_thread();
+ unsafe {
+ self.observer
+ .OnStopRequest(self.coerce(), self.download_status_nsresult.get());
+ }
+ }
+
+ /// To be called after a cancel or complete task has run successfully. If
+ /// this is the only BitsRequest running, this will shut down
+ /// BitsService's command thread, destroying the BitsClient.
+ pub fn on_finished(&self) {
+ if self.finished.get() {
+ return;
+ }
+ self.finished.set(true);
+ self.bits_service.dec_request_count();
+ }
+
+ // Return the same thing for GetBitsId() and GetName().
+ xpcom_method!(
+ maybe_get_bits_id => GetBitsId() -> nsACString
+ );
+ xpcom_method!(
+ maybe_get_bits_id => GetName() -> nsACString
+ );
+ fn maybe_get_bits_id(&self) -> Result<nsCString, nsresult> {
+ Ok(self.get_bits_id())
+ }
+ pub fn get_bits_id(&self) -> nsCString {
+ nsCString::from(self.bits_id.to_string())
+ }
+
+ xpcom_method!(
+ get_bits_transfer_error_nsIBitsRequest => GetTransferError() -> i32
+ );
+ #[allow(non_snake_case)]
+ fn get_bits_transfer_error_nsIBitsRequest(&self) -> Result<i32, nsresult> {
+ let error_type = match self.download_status_error_type.get() {
+ None => nsIBits::ERROR_TYPE_SUCCESS,
+ Some(error_type) => error_type.bits_code(),
+ };
+ Ok(error_type)
+ }
+
+ xpcom_method!(
+ is_pending => IsPending() -> bool
+ );
+ fn is_pending(&self) -> Result<bool, nsresult> {
+ Ok(self.download_pending.get())
+ }
+
+ xpcom_method!(
+ get_status_nsIRequest => GetStatus() -> nsresult
+ );
+ #[allow(non_snake_case)]
+ fn get_status_nsIRequest(&self) -> Result<nsresult, nsresult> {
+ Ok(self.get_status())
+ }
+ pub fn get_status(&self) -> nsresult {
+ self.download_status_nsresult.get()
+ }
+
+ nsIBitsRequest_method!(
+ [Action::SetMonitorInterval]
+ change_monitor_interval => ChangeMonitorInterval(update_interval_ms: u32)
+ );
+ fn change_monitor_interval(
+ &self,
+ update_interval_ms: u32,
+ callback: &nsIBitsCallback,
+ ) -> Result<(), BitsTaskError> {
+ if update_interval_ms == 0 || update_interval_ms >= self.monitor_timeout_ms {
+ return Err(BitsTaskError::new(
+ InvalidArgument,
+ Action::SetMonitorInterval,
+ Pretask,
+ ));
+ }
+ if self.request_has_transferred() {
+ return Err(BitsTaskError::new(
+ TransferAlreadyComplete,
+ Action::SetMonitorInterval,
+ Pretask,
+ ));
+ }
+
+ let task: Box<ChangeMonitorIntervalTask> = Box::new(ChangeMonitorIntervalTask::new(
+ RefPtr::new(self),
+ self.bits_id.clone(),
+ update_interval_ms,
+ RefPtr::new(callback),
+ ));
+
+ self.bits_service.dispatch_runnable_to_command_thread(
+ task,
+ "BitsRequest::change_monitor_interval",
+ Action::SetMonitorInterval,
+ )
+ }
+
+ nsIBitsRequest_method!(
+ [Action::Cancel]
+ cancel_nsIBitsRequest => CancelAsync(status: nsresult)
+ );
+ #[allow(non_snake_case)]
+ fn cancel_nsIBitsRequest(
+ &self,
+ status: nsresult,
+ callback: &nsIBitsCallback,
+ ) -> Result<(), BitsTaskError> {
+ self.cancel(status, Some(RefPtr::new(callback)))
+ }
+ xpcom_method!(
+ cancel_nsIRequest => Cancel(status: nsresult)
+ );
+ #[allow(non_snake_case)]
+ fn cancel_nsIRequest(&self, status: nsresult) -> Result<(), BitsTaskError> {
+ self.cancel(status, None)
+ }
+
+ fn cancel(
+ &self,
+ status: nsresult,
+ callback: Option<RefPtr<nsIBitsCallback>>,
+ ) -> Result<(), BitsTaskError> {
+ if status.clone().succeeded() {
+ return Err(BitsTaskError::new(InvalidArgument, Action::Cancel, Pretask));
+ }
+ if self.request_has_completed() {
+ return Err(BitsTaskError::new(
+ TransferAlreadyComplete,
+ Action::Cancel,
+ Pretask,
+ ));
+ }
+
+ // If the transfer is still in a success state, cancelling it should move it to the failure
+ // state that was passed. But if the transfer already failed, the only reason to call cancel
+ // is to remove the job from BITS. So in that case, we should keep the failure status that
+ // we already have.
+ let maybe_status: Option<nsresult> = if self.download_status_nsresult.get().failed() {
+ None
+ } else {
+ Some(status)
+ };
+
+ if self.cancel_action.get() != CancelAction::NotInProgress {
+ return Err(BitsTaskError::new(
+ OperationAlreadyInProgress,
+ Action::Cancel,
+ Pretask,
+ ));
+ }
+ self.cancel_action
+ .set(CancelAction::InProgress(maybe_status));
+
+ let task: Box<CancelTask> = Box::new(CancelTask::new(
+ RefPtr::new(self),
+ self.bits_id.clone(),
+ callback,
+ ));
+
+ self.bits_service.dispatch_runnable_to_command_thread(
+ task,
+ "BitsRequest::cancel",
+ Action::Cancel,
+ )
+ }
+
+ /// This function must be called when a cancel action completes.
+ ///
+ /// See the documentation for CancelAction for details.
+ pub fn finish_cancel_action(&self, cancelled_successfully: bool) {
+ let (maybe_status, transfer_ended) = match self.cancel_action.get() {
+ CancelAction::InProgress(maybe_status) => (maybe_status, false),
+ CancelAction::RequestEndedWhileInProgress(maybe_status) => (maybe_status, true),
+ _ => {
+ error!("End of cancel action, but cancel action is not in progress!");
+ return;
+ }
+ };
+ info!(
+ "Finishing cancel action. cancel success = {}",
+ cancelled_successfully
+ );
+ if cancelled_successfully {
+ // If no status was provided, it is because this cancel action removed the BITS job
+ // after the job had already failed. Keep the original error codes.
+ if let Some(status) = maybe_status {
+ self.download_status_nsresult.set(status);
+ self.download_status_error_type
+ .set(Some(BitsStateCancelled));
+ }
+ }
+
+ let next_stage = if cancelled_successfully && !transfer_ended {
+ // This signals on_stop not to allow the status codes set above to
+ // be overridden by the ones passed to it.
+ CancelAction::RequestEndPending
+ } else {
+ CancelAction::NotInProgress
+ };
+ self.cancel_action.set(next_stage);
+
+ if cancelled_successfully {
+ self.on_finished();
+ }
+
+ if transfer_ended {
+ info!("Running deferred OnStopRequest");
+ self.on_stop(None);
+ }
+ }
+
+ nsIBitsRequest_method!(
+ [Action::SetPriority]
+ set_priority_high => SetPriorityHigh()
+ );
+ fn set_priority_high(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
+ self.set_priority(Priority::High, callback)
+ }
+
+ nsIBitsRequest_method!(
+ [Action::SetPriority]
+ set_priority_low => SetPriorityLow()
+ );
+ fn set_priority_low(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
+ self.set_priority(Priority::Low, callback)
+ }
+
+ fn set_priority(
+ &self,
+ priority: Priority,
+ callback: &nsIBitsCallback,
+ ) -> Result<(), BitsTaskError> {
+ if self.request_has_transferred() {
+ return Err(BitsTaskError::new(
+ TransferAlreadyComplete,
+ Action::SetPriority,
+ Pretask,
+ ));
+ }
+
+ let task: Box<SetPriorityTask> = Box::new(SetPriorityTask::new(
+ RefPtr::new(self),
+ self.bits_id.clone(),
+ priority,
+ RefPtr::new(callback),
+ ));
+
+ self.bits_service.dispatch_runnable_to_command_thread(
+ task,
+ "BitsRequest::set_priority",
+ Action::SetPriority,
+ )
+ }
+
+ nsIBitsRequest_method!(
+ [Action::SetNoProgressTimeout]
+ set_no_progress_timeout => SetNoProgressTimeout(timeout_secs: u32)
+ );
+ fn set_no_progress_timeout(
+ &self,
+ timeout_secs: u32,
+ callback: &nsIBitsCallback,
+ ) -> Result<(), BitsTaskError> {
+ if self.request_has_transferred() {
+ return Err(BitsTaskError::new(
+ TransferAlreadyComplete,
+ Action::SetNoProgressTimeout,
+ Pretask,
+ ));
+ }
+
+ let task: Box<SetNoProgressTimeoutTask> = Box::new(SetNoProgressTimeoutTask::new(
+ RefPtr::new(self),
+ self.bits_id.clone(),
+ timeout_secs,
+ RefPtr::new(callback),
+ ));
+
+ self.bits_service.dispatch_runnable_to_command_thread(
+ task,
+ "BitsRequest::set_no_progress_timeout",
+ Action::SetNoProgressTimeout,
+ )
+ }
+
+ nsIBitsRequest_method!(
+ [Action::Complete]
+ complete => Complete()
+ );
+ fn complete(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
+ if self.request_has_completed() {
+ return Err(BitsTaskError::new(
+ TransferAlreadyComplete,
+ Action::Complete,
+ Pretask,
+ ));
+ }
+
+ let task: Box<CompleteTask> = Box::new(CompleteTask::new(
+ RefPtr::new(self),
+ self.bits_id.clone(),
+ RefPtr::new(callback),
+ ));
+
+ self.bits_service.dispatch_runnable_to_command_thread(
+ task,
+ "BitsRequest::complete",
+ Action::Complete,
+ )
+ }
+
+ nsIBitsRequest_method!(
+ [Action::Suspend]
+ suspend_nsIBitsRequest => SuspendAsync()
+ );
+ #[allow(non_snake_case)]
+ fn suspend_nsIBitsRequest(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
+ self.suspend(Some(RefPtr::new(callback)))
+ }
+ xpcom_method!(
+ suspend_nsIRequest => Suspend()
+ );
+ #[allow(non_snake_case)]
+ fn suspend_nsIRequest(&self) -> Result<(), BitsTaskError> {
+ self.suspend(None)
+ }
+
+ fn suspend(&self, callback: Option<RefPtr<nsIBitsCallback>>) -> Result<(), BitsTaskError> {
+ if self.request_has_transferred() {
+ return Err(BitsTaskError::new(
+ TransferAlreadyComplete,
+ Action::Suspend,
+ Pretask,
+ ));
+ }
+
+ let task: Box<SuspendTask> = Box::new(SuspendTask::new(
+ RefPtr::new(self),
+ self.bits_id.clone(),
+ callback,
+ ));
+
+ self.bits_service.dispatch_runnable_to_command_thread(
+ task,
+ "BitsRequest::suspend",
+ Action::Suspend,
+ )
+ }
+
+ nsIBitsRequest_method!(
+ [Action::Resume]
+ resume_nsIBitsRequest => ResumeAsync()
+ );
+ #[allow(non_snake_case)]
+ fn resume_nsIBitsRequest(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
+ self.resume(Some(RefPtr::new(callback)))
+ }
+ xpcom_method!(
+ resume_nsIRequest => Resume()
+ );
+ #[allow(non_snake_case)]
+ fn resume_nsIRequest(&self) -> Result<(), BitsTaskError> {
+ self.resume(None)
+ }
+
+ fn resume(&self, callback: Option<RefPtr<nsIBitsCallback>>) -> Result<(), BitsTaskError> {
+ if self.request_has_transferred() {
+ return Err(BitsTaskError::new(
+ TransferAlreadyComplete,
+ Action::Resume,
+ Pretask,
+ ));
+ }
+
+ let task: Box<ResumeTask> = Box::new(ResumeTask::new(
+ RefPtr::new(self),
+ self.bits_id.clone(),
+ callback,
+ ));
+
+ self.bits_service.dispatch_runnable_to_command_thread(
+ task,
+ "BitsRequest::resume",
+ Action::Resume,
+ )
+ }
+
+ xpcom_method!(
+ get_load_group => GetLoadGroup() -> *const nsILoadGroup
+ );
+
+ /**
+ * As stated in nsIBits.idl, nsIBits interfaces are not expected to
+ * implement the loadGroup or loadFlags attributes. This implementation
+ * provides only null implementations only for these methods.
+ */
+ fn get_load_group(&self) -> Result<RefPtr<nsILoadGroup>, nsresult> {
+ Err(NS_ERROR_NOT_IMPLEMENTED)
+ }
+
+ xpcom_method!(
+ set_load_group => SetLoadGroup(_load_group: *const nsILoadGroup)
+ );
+ fn set_load_group(&self, _load_group: &nsILoadGroup) -> Result<(), nsresult> {
+ Err(NS_ERROR_NOT_IMPLEMENTED)
+ }
+
+ xpcom_method!(
+ get_load_flags => GetLoadFlags() -> nsLoadFlags
+ );
+ fn get_load_flags(&self) -> Result<nsLoadFlags, nsresult> {
+ Err(NS_ERROR_NOT_IMPLEMENTED)
+ }
+
+ xpcom_method!(
+ set_load_flags => SetLoadFlags(_load_flags: nsLoadFlags)
+ );
+ fn set_load_flags(&self, _load_flags: nsLoadFlags) -> Result<(), nsresult> {
+ Err(NS_ERROR_NOT_IMPLEMENTED)
+ }
+
+ xpcom_method!(
+ get_trr_mode => GetTRRMode() -> u32
+ );
+ fn get_trr_mode(&self) -> Result<u32, nsresult> {
+ Err(NS_ERROR_NOT_IMPLEMENTED)
+ }
+
+ xpcom_method!(
+ set_trr_mode => SetTRRMode(_trr_mode: u32)
+ );
+ fn set_trr_mode(&self, _trr_mode: u32) -> Result<(), nsresult> {
+ Err(NS_ERROR_NOT_IMPLEMENTED)
+ }
+
+ xpcom_method!(
+ get_canceled_reason => GetCanceledReason() -> nsACString
+ );
+ fn get_canceled_reason(&self) -> Result<nsCString, nsresult> {
+ Err(NS_ERROR_NOT_IMPLEMENTED)
+ }
+
+ xpcom_method!(
+ set_canceled_reason => SetCanceledReason(_reason: *const nsACString)
+ );
+ fn set_canceled_reason(&self, _reason: *const nsACString) -> Result<(), nsresult> {
+ Err(NS_ERROR_NOT_IMPLEMENTED)
+ }
+
+ xpcom_method!(
+ cancel_with_reason_nsIRequest => CancelWithReason(status: nsresult, _reason: *const nsACString)
+ );
+ #[allow(non_snake_case)]
+ fn cancel_with_reason_nsIRequest(
+ &self,
+ status: nsresult,
+ _reason: *const nsACString,
+ ) -> Result<(), BitsTaskError> {
+ self.cancel(status, None)
+ }
+}
+
+impl Drop for BitsRequest {
+ fn drop(&mut self) {
+ // Make sure that the monitor thread gets cleaned up.
+ self.shutdown_monitor_thread();
+ // Make sure we tell BitsService that we are done with the command thread.
+ self.on_finished();
+ }
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/string.rs b/toolkit/components/bitsdownload/src/bits_interface/string.rs
new file mode 100644
index 0000000000..c3111b7cba
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/string.rs
@@ -0,0 +1,80 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+use super::{
+ action::Action,
+ error::{BitsTaskError, ErrorStage, ErrorType},
+};
+
+use bits_client::Guid;
+use nsstring::nsCString;
+use std::ffi::OsString;
+use std::{str, str::FromStr};
+
+/// This function is fallible, and the consumers would prefer a BitsTaskError
+/// in the failure case. To facilitate that, this function takes some params
+/// that give it the data necessary to construct the BitsTaskError if it fails.
+/// If it succeeds, those values will be unused.
+#[allow(non_snake_case)]
+pub fn nsCString_to_String(
+ value: &nsCString,
+ error_action: Action,
+ error_stage: ErrorStage,
+) -> Result<String, BitsTaskError> {
+ match String::from_utf8(value[..].to_vec()) {
+ Ok(s) => Ok(s),
+ Err(_) => Err(BitsTaskError::new(
+ ErrorType::NoUtf8Conversion,
+ error_action,
+ error_stage,
+ )),
+ }
+}
+
+/// This function is fallible, and the consumers would prefer a BitsTaskError
+/// in the failure case. To facilitate that, this function takes some params
+/// that give it the data necessary to construct the BitsTaskError if it fails.
+/// If it succeeds, those values will be unused.
+#[allow(non_snake_case)]
+pub fn nsCString_to_OsString(
+ value: &nsCString,
+ error_action: Action,
+ error_stage: ErrorStage,
+) -> Result<OsString, BitsTaskError> {
+ Ok(OsString::from(nsCString_to_String(
+ value,
+ error_action,
+ error_stage,
+ )?))
+}
+
+/// This function is fallible, and the consumers would prefer a BitsTaskError
+/// in the failure case. To facilitate that, this function takes some params
+/// that give it the data necessary to construct the BitsTaskError if it fails.
+/// If it succeeds, those values will be unused.
+#[allow(non_snake_case)]
+pub fn Guid_from_nsCString(
+ value: &nsCString,
+ error_action: Action,
+ error_stage: ErrorStage,
+) -> Result<Guid, BitsTaskError> {
+ let vector = &value[..].to_vec();
+ let string = match str::from_utf8(vector) {
+ Ok(s) => s,
+ Err(_) => {
+ return Err(BitsTaskError::new(
+ ErrorType::NoUtf8Conversion,
+ error_action,
+ error_stage,
+ ));
+ }
+ };
+ Guid::from_str(string).map_err(|comedy_error| {
+ BitsTaskError::from_comedy(
+ ErrorType::InvalidGuid,
+ error_action,
+ error_stage,
+ comedy_error,
+ )
+ })
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/task/client.rs b/toolkit/components/bitsdownload/src/bits_interface/task/client.rs
new file mode 100644
index 0000000000..edbf4d0698
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/task/client.rs
@@ -0,0 +1,102 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+use super::{
+ action::Action,
+ error::{BitsTaskError, ErrorStage::CommandThread, ErrorType::MissingBitsClient},
+ string::nsCString_to_OsString,
+};
+
+use bits_client::BitsClient;
+use nsstring::nsCString;
+use std::cell::Cell;
+
+thread_local! {
+ // This is used to store the `BitsClient` on the Command thread.
+ // Keeping it here solves the problem of how to allow multiple runnables to
+ // be simultaneously queued on the Command thread while giving them all
+ // access to the same `BitsClient`.
+ static BITS_CLIENT: Cell<Option<BitsClient>> = Cell::new(None);
+}
+
+/// This structure holds the data needed to initialize `BitsClient` and
+/// `BitsMonitorClient`.
+#[derive(Debug, Clone)]
+pub struct ClientInitData {
+ pub job_name: nsCString,
+ pub save_path_prefix: nsCString,
+ pub monitor_timeout_ms: u32,
+}
+
+impl ClientInitData {
+ pub fn new(
+ job_name: nsCString,
+ save_path_prefix: nsCString,
+ monitor_timeout_ms: u32,
+ ) -> ClientInitData {
+ ClientInitData {
+ job_name,
+ save_path_prefix,
+ monitor_timeout_ms,
+ }
+ }
+}
+
+/// This function constructs a `BitsClient`, if one does not already exist. If
+/// the `BitsClient` cannot be constructed, a `BitsTaskError` will be returned.
+/// If the `BitsClient` could be obtained, then the function then calls the
+/// closure passed to it, passing a mutable reference to the `BitsClient`.
+/// This function will then return whatever the closure returned, which must be
+/// a `Result<_, BitsTaskError>`.
+pub fn with_maybe_new_bits_client<F, R>(
+ init_data: &ClientInitData,
+ action: Action,
+ closure: F,
+) -> Result<R, BitsTaskError>
+where
+ F: FnOnce(&mut BitsClient) -> Result<R, BitsTaskError>,
+{
+ _with_bits_client(Some(init_data), action, closure)
+}
+
+/// This function assumes that a `BitsClient` has already been constructed. If
+/// there is not one available, a `BitsTaskError` will be returned. Otherwise,
+/// the function calls the closure passed to it, passing a mutable reference to
+/// the `BitsClient`. This function will then return whatever the closure
+/// returned, which must be a `Result<_, BitsTaskError>`.
+pub fn with_bits_client<F, R>(action: Action, closure: F) -> Result<R, BitsTaskError>
+where
+ F: FnOnce(&mut BitsClient) -> Result<R, BitsTaskError>,
+{
+ _with_bits_client(None, action, closure)
+}
+
+fn _with_bits_client<F, R>(
+ maybe_init_data: Option<&ClientInitData>,
+ action: Action,
+ closure: F,
+) -> Result<R, BitsTaskError>
+where
+ F: FnOnce(&mut BitsClient) -> Result<R, BitsTaskError>,
+{
+ BITS_CLIENT.with(|cell| {
+ let maybe_client = cell.take();
+ let mut client = match (maybe_client, maybe_init_data) {
+ (Some(r), _) => r,
+ (None, Some(init_data)) => {
+ // Immediately invoked function to allow for the ? operator
+ BitsClient::new(
+ nsCString_to_OsString(&init_data.job_name, action, CommandThread)?,
+ nsCString_to_OsString(&init_data.save_path_prefix, action, CommandThread)?,
+ )
+ .map_err(|pipe_error| BitsTaskError::from_pipe(action, pipe_error))?
+ }
+ (None, None) => {
+ return Err(BitsTaskError::new(MissingBitsClient, action, CommandThread));
+ }
+ };
+ let result = closure(&mut client);
+ cell.set(Some(client));
+ result
+ })
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/task/from_threadbound.rs b/toolkit/components/bitsdownload/src/bits_interface/task/from_threadbound.rs
new file mode 100644
index 0000000000..2cf2e9189a
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/task/from_threadbound.rs
@@ -0,0 +1,125 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+use super::{
+ action::Action,
+ error::{BitsTaskError, ErrorStage, ErrorType},
+};
+use log::warn;
+use xpcom::{RefCounted, ThreadBoundRefPtr};
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum DataType {
+ Callback,
+ BitsService,
+ BitsRequest,
+ Observer,
+ Context,
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+enum GetThreadboundError {
+ Missing,
+ WrongThread,
+}
+
+impl DataType {
+ fn error_type(&self, error: GetThreadboundError) -> ErrorType {
+ match self {
+ DataType::Callback => match error {
+ GetThreadboundError::Missing => ErrorType::MissingCallback,
+ GetThreadboundError::WrongThread => ErrorType::CallbackOnWrongThread,
+ },
+ DataType::BitsService => match error {
+ GetThreadboundError::Missing => ErrorType::MissingBitsService,
+ GetThreadboundError::WrongThread => ErrorType::BitsServiceOnWrongThread,
+ },
+ DataType::BitsRequest => match error {
+ GetThreadboundError::Missing => ErrorType::MissingBitsRequest,
+ GetThreadboundError::WrongThread => ErrorType::BitsRequestOnWrongThread,
+ },
+ DataType::Observer => match error {
+ GetThreadboundError::Missing => ErrorType::MissingObserver,
+ GetThreadboundError::WrongThread => ErrorType::ObserverOnWrongThread,
+ },
+ DataType::Context => match error {
+ GetThreadboundError::Missing => ErrorType::MissingContext,
+ GetThreadboundError::WrongThread => ErrorType::ContextOnWrongThread,
+ },
+ }
+ }
+
+ fn name(&self) -> &'static str {
+ match self {
+ DataType::Callback => "Callback",
+ DataType::BitsService => "BITS Service",
+ DataType::BitsRequest => "BITS Request",
+ DataType::Observer => "Observer",
+ DataType::Context => "Context",
+ }
+ }
+}
+
+/// Given a reference to a threadbound option
+/// (i.e. `&Option<ThreadBoundRefPtr<_>>`), this function will attempt to
+/// retrieve a reference to the value stored within. If it is not available
+/// (option is `None` or value is on the wrong thread), `None` is returned
+/// instead.
+pub fn get_from_threadbound_option<T>(
+ maybe_threadbound: &Option<ThreadBoundRefPtr<T>>,
+ data_type: DataType,
+ action: Action,
+) -> Option<&T>
+where
+ T: RefCounted + 'static,
+{
+ maybe_threadbound.as_ref().and_then(|threadbound| {
+ let maybe_reference = threadbound.get_ref();
+ if maybe_reference.is_none() {
+ warn!(
+ "Unexpected error {}: {} is on the wrong thread",
+ action.description(),
+ data_type.name(),
+ );
+ }
+ maybe_reference
+ })
+}
+
+/// Given a reference to a threadbound option
+/// (i.e. `&Option<ThreadBoundRefPtr<_>>`), this function will attempt to
+/// retrieve a reference to the value stored within. If it is not available
+/// (option is `None` or value is on the wrong thread), a `BitsTaskError` is
+/// returned instead.
+pub fn expect_from_threadbound_option<T>(
+ maybe_threadbound: &Option<ThreadBoundRefPtr<T>>,
+ data_type: DataType,
+ action: Action,
+) -> Result<&T, BitsTaskError>
+where
+ T: RefCounted + 'static,
+{
+ match maybe_threadbound.as_ref() {
+ Some(threadbound) => {
+ match threadbound.get_ref() {
+ Some(reference) => Ok(reference),
+ None => Err(BitsTaskError::new(
+ data_type.error_type(GetThreadboundError::WrongThread),
+ action,
+ // Retrieving data from threadbounds all happens on the main thread.
+ // No data is ever bound to other threads so there would be no
+ // reason to retrieve it there.
+ ErrorStage::MainThread,
+ )),
+ }
+ }
+ None => Err(BitsTaskError::new(
+ data_type.error_type(GetThreadboundError::Missing),
+ action,
+ // Retrieving data from threadbounds all happens on the main thread.
+ // No data is ever bound to other threads so there would be no
+ // reason to retrieve it there.
+ ErrorStage::MainThread,
+ )),
+ }
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/task/mod.rs b/toolkit/components/bitsdownload/src/bits_interface/task/mod.rs
new file mode 100644
index 0000000000..b6b96d887b
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/task/mod.rs
@@ -0,0 +1,18 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+mod from_threadbound;
+
+use super::{action, dispatch_callback, error, request::BitsRequest, string, BitsService};
+
+mod client;
+pub use self::client::ClientInitData;
+
+mod service_task;
+pub use self::service_task::{MonitorDownloadTask, StartDownloadTask};
+
+mod request_task;
+pub use self::request_task::{
+ CancelTask, ChangeMonitorIntervalTask, CompleteTask, Priority, ResumeTask,
+ SetNoProgressTimeoutTask, SetPriorityTask, SuspendTask,
+};
diff --git a/toolkit/components/bitsdownload/src/bits_interface/task/request_task.rs b/toolkit/components/bitsdownload/src/bits_interface/task/request_task.rs
new file mode 100644
index 0000000000..f0fd331ed0
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/task/request_task.rs
@@ -0,0 +1,425 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+use super::{
+ action::{Action, RequestAction},
+ client::with_bits_client,
+ dispatch_callback::{
+ maybe_dispatch_via_callback, CallbackExpected, CallbackOptional, IsCallbackExpected,
+ },
+ error::BitsTaskError,
+ from_threadbound::{expect_from_threadbound_option, DataType},
+ BitsRequest,
+};
+
+use bits_client::{BitsClient, Guid};
+use crossbeam_utils::atomic::AtomicCell;
+use log::info;
+use moz_task::Task;
+use nserror::nsresult;
+use xpcom::{interfaces::nsIBitsCallback, RefPtr, ThreadBoundRefPtr};
+
+type RunFn<D> = fn(Guid, &D, &mut BitsClient) -> Result<(), BitsTaskError>;
+type DoneFn = fn(&BitsRequest, bool) -> Result<(), BitsTaskError>;
+
+pub struct RequestTask<D> {
+ request: AtomicCell<Option<ThreadBoundRefPtr<BitsRequest>>>,
+ guid: Guid,
+ action: RequestAction,
+ task_data: D,
+ run_fn: RunFn<D>,
+ maybe_done_fn: Option<DoneFn>,
+ callback: AtomicCell<Option<ThreadBoundRefPtr<nsIBitsCallback>>>,
+ callback_presence: IsCallbackExpected,
+ result: AtomicCell<Option<Result<(), BitsTaskError>>>,
+}
+
+impl<D> RequestTask<D>
+where
+ D: Sync + Send,
+{
+ pub fn new(
+ request: RefPtr<BitsRequest>,
+ guid: Guid,
+ action: RequestAction,
+ task_data: D,
+ run_fn: RunFn<D>,
+ maybe_done_fn: Option<DoneFn>,
+ callback: Option<RefPtr<nsIBitsCallback>>,
+ callback_presence: IsCallbackExpected,
+ ) -> RequestTask<D> {
+ RequestTask {
+ request: AtomicCell::new(Some(ThreadBoundRefPtr::new(request))),
+ guid,
+ action,
+ task_data,
+ run_fn,
+ maybe_done_fn,
+ callback: AtomicCell::new(callback.map(ThreadBoundRefPtr::new)),
+ result: AtomicCell::new(None),
+ callback_presence,
+ }
+ }
+}
+
+impl<D> Task for RequestTask<D> {
+ fn run(&self) {
+ let result = with_bits_client(self.action.into(), |client| {
+ (self.run_fn)(self.guid.clone(), &self.task_data, client)
+ });
+ self.result.store(Some(result));
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ // If TaskRunnable.run() calls Task.done() to return a result
+ // on the main thread before TaskRunnable.run() returns on the worker
+ // thread, then the Task will get dropped on the worker thread.
+ //
+ // But the callback is an nsXPCWrappedJS that isn't safe to release
+ // on the worker thread. So we move it out of the Task here to ensure
+ // it gets released on the main thread.
+ let maybe_tb_callback = self.callback.swap(None);
+ // It also isn't safe to drop the BitsRequest RefPtr off-thread,
+ // because BitsRequest refcounting is non-atomic
+ let maybe_tb_request = self.request.swap(None);
+
+ let action: Action = self.action.into();
+ let maybe_callback =
+ expect_from_threadbound_option(&maybe_tb_callback, DataType::Callback, action);
+
+ // Immediately invoked function expression to allow for the ? operator
+ let result: Result<(), BitsTaskError> = (|| {
+ let request =
+ expect_from_threadbound_option(&maybe_tb_request, DataType::BitsRequest, action)?;
+
+ let maybe_result = self.result.swap(None);
+
+ let success = if let Some(result) = maybe_result.as_ref() {
+ result.is_ok()
+ } else {
+ false
+ };
+
+ if let Some(done_fn) = self.maybe_done_fn {
+ done_fn(request, success)?;
+ }
+
+ maybe_result.ok_or_else(|| BitsTaskError::missing_result(action))?
+ })();
+ info!("BITS Request Task completed: {:?}", result);
+ maybe_dispatch_via_callback(result, maybe_callback, self.callback_presence)
+ }
+}
+
+pub struct CompleteTask(RequestTask<()>);
+
+impl Task for CompleteTask {
+ fn run(&self) {
+ self.0.run();
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ self.0.done()
+ }
+}
+
+impl CompleteTask {
+ pub fn new(
+ request: RefPtr<BitsRequest>,
+ id: Guid,
+ callback: RefPtr<nsIBitsCallback>,
+ ) -> CompleteTask {
+ CompleteTask(RequestTask::new(
+ request,
+ id,
+ RequestAction::Complete,
+ (),
+ CompleteTask::run_fn,
+ Some(CompleteTask::done_fn),
+ Some(callback),
+ CallbackExpected,
+ ))
+ }
+
+ fn run_fn(id: Guid, _data: &(), client: &mut BitsClient) -> Result<(), BitsTaskError> {
+ client
+ .complete_job(id)
+ .map_err(|pipe_error| BitsTaskError::from_pipe(Action::Complete, pipe_error))??;
+ Ok(())
+ }
+
+ fn done_fn(request: &BitsRequest, success: bool) -> Result<(), BitsTaskError> {
+ if success {
+ request.on_finished();
+ }
+ Ok(())
+ }
+}
+
+pub struct CancelTask(RequestTask<()>);
+
+impl Task for CancelTask {
+ fn run(&self) {
+ self.0.run();
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ self.0.done()
+ }
+}
+
+impl CancelTask {
+ pub fn new(
+ request: RefPtr<BitsRequest>,
+ id: Guid,
+ callback: Option<RefPtr<nsIBitsCallback>>,
+ ) -> CancelTask {
+ let callback_presence = if callback.is_some() {
+ CallbackExpected
+ } else {
+ CallbackOptional
+ };
+
+ CancelTask(RequestTask::new(
+ request,
+ id,
+ RequestAction::Cancel,
+ (),
+ CancelTask::run_fn,
+ Some(CancelTask::done_fn),
+ callback,
+ callback_presence,
+ ))
+ }
+
+ fn run_fn(id: Guid, _data: &(), client: &mut BitsClient) -> Result<(), BitsTaskError> {
+ client
+ .cancel_job(id)
+ .map_err(|pipe_error| BitsTaskError::from_pipe(Action::Cancel, pipe_error))??;
+ Ok(())
+ }
+
+ fn done_fn(request: &BitsRequest, success: bool) -> Result<(), BitsTaskError> {
+ request.finish_cancel_action(success);
+ Ok(())
+ }
+}
+
+pub struct SuspendTask(RequestTask<()>);
+
+impl Task for SuspendTask {
+ fn run(&self) {
+ self.0.run();
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ self.0.done()
+ }
+}
+
+impl SuspendTask {
+ pub fn new(
+ request: RefPtr<BitsRequest>,
+ id: Guid,
+ callback: Option<RefPtr<nsIBitsCallback>>,
+ ) -> SuspendTask {
+ let callback_presence = if callback.is_some() {
+ CallbackExpected
+ } else {
+ CallbackOptional
+ };
+
+ SuspendTask(RequestTask::new(
+ request,
+ id,
+ RequestAction::Suspend,
+ (),
+ SuspendTask::run_fn,
+ None,
+ callback,
+ callback_presence,
+ ))
+ }
+
+ fn run_fn(id: Guid, _data: &(), client: &mut BitsClient) -> Result<(), BitsTaskError> {
+ client
+ .suspend_job(id)
+ .map_err(|pipe_error| BitsTaskError::from_pipe(Action::Suspend, pipe_error))??;
+ Ok(())
+ }
+}
+
+pub struct ResumeTask(RequestTask<()>);
+
+impl Task for ResumeTask {
+ fn run(&self) {
+ self.0.run();
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ self.0.done()
+ }
+}
+
+impl ResumeTask {
+ pub fn new(
+ request: RefPtr<BitsRequest>,
+ id: Guid,
+ callback: Option<RefPtr<nsIBitsCallback>>,
+ ) -> ResumeTask {
+ let callback_presence = if callback.is_some() {
+ CallbackExpected
+ } else {
+ CallbackOptional
+ };
+
+ ResumeTask(RequestTask::new(
+ request,
+ id,
+ RequestAction::Resume,
+ (),
+ ResumeTask::run_fn,
+ None,
+ callback,
+ callback_presence,
+ ))
+ }
+
+ fn run_fn(id: Guid, _data: &(), client: &mut BitsClient) -> Result<(), BitsTaskError> {
+ client
+ .resume_job(id)
+ .map_err(|pipe_error| BitsTaskError::from_pipe(Action::Resume, pipe_error))??;
+ Ok(())
+ }
+}
+
+pub struct ChangeMonitorIntervalTask(RequestTask<u32>);
+
+impl Task for ChangeMonitorIntervalTask {
+ fn run(&self) {
+ self.0.run();
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ self.0.done()
+ }
+}
+
+impl ChangeMonitorIntervalTask {
+ pub fn new(
+ request: RefPtr<BitsRequest>,
+ id: Guid,
+ update_interval_ms: u32,
+ callback: RefPtr<nsIBitsCallback>,
+ ) -> ChangeMonitorIntervalTask {
+ ChangeMonitorIntervalTask(RequestTask::new(
+ request,
+ id,
+ RequestAction::SetMonitorInterval,
+ update_interval_ms,
+ ChangeMonitorIntervalTask::run_fn,
+ None,
+ Some(callback),
+ CallbackExpected,
+ ))
+ }
+
+ fn run_fn(
+ id: Guid,
+ update_interval_ms: &u32,
+ client: &mut BitsClient,
+ ) -> Result<(), BitsTaskError> {
+ client
+ .set_update_interval(id, *update_interval_ms)
+ .map_err(|pipe_error| {
+ BitsTaskError::from_pipe(Action::SetMonitorInterval, pipe_error)
+ })??;
+ Ok(())
+ }
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum Priority {
+ High,
+ Low,
+}
+
+pub struct SetPriorityTask(RequestTask<Priority>);
+
+impl Task for SetPriorityTask {
+ fn run(&self) {
+ self.0.run();
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ self.0.done()
+ }
+}
+
+impl SetPriorityTask {
+ pub fn new(
+ request: RefPtr<BitsRequest>,
+ id: Guid,
+ priority: Priority,
+ callback: RefPtr<nsIBitsCallback>,
+ ) -> SetPriorityTask {
+ SetPriorityTask(RequestTask::new(
+ request,
+ id,
+ RequestAction::SetPriority,
+ priority,
+ SetPriorityTask::run_fn,
+ None,
+ Some(callback),
+ CallbackExpected,
+ ))
+ }
+
+ fn run_fn(id: Guid, priority: &Priority, client: &mut BitsClient) -> Result<(), BitsTaskError> {
+ client
+ .set_job_priority(id, *priority == Priority::High)
+ .map_err(|pipe_error| BitsTaskError::from_pipe(Action::SetPriority, pipe_error))??;
+ Ok(())
+ }
+}
+
+pub struct SetNoProgressTimeoutTask(RequestTask<u32>);
+
+impl Task for SetNoProgressTimeoutTask {
+ fn run(&self) {
+ self.0.run();
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ self.0.done()
+ }
+}
+
+impl SetNoProgressTimeoutTask {
+ pub fn new(
+ request: RefPtr<BitsRequest>,
+ id: Guid,
+ timeout_secs: u32,
+ callback: RefPtr<nsIBitsCallback>,
+ ) -> SetNoProgressTimeoutTask {
+ SetNoProgressTimeoutTask(RequestTask::new(
+ request,
+ id,
+ RequestAction::SetNoProgressTimeout,
+ timeout_secs,
+ SetNoProgressTimeoutTask::run_fn,
+ None,
+ Some(callback),
+ CallbackExpected,
+ ))
+ }
+
+ fn run_fn(id: Guid, timeout_secs: &u32, client: &mut BitsClient) -> Result<(), BitsTaskError> {
+ client
+ .set_no_progress_timeout(id, *timeout_secs)
+ .map_err(|pipe_error| {
+ BitsTaskError::from_pipe(Action::SetNoProgressTimeout, pipe_error)
+ })??;
+ Ok(())
+ }
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/task/service_task.rs b/toolkit/components/bitsdownload/src/bits_interface/task/service_task.rs
new file mode 100644
index 0000000000..c66f127f7a
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/task/service_task.rs
@@ -0,0 +1,332 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+use super::{
+ action::{
+ Action,
+ Action::{MonitorDownload, StartDownload},
+ ServiceAction,
+ },
+ client::{with_maybe_new_bits_client, ClientInitData},
+ dispatch_callback::{maybe_dispatch_request_via_callback, CallbackExpected},
+ error::{BitsTaskError, ErrorStage::CommandThread},
+ from_threadbound::{expect_from_threadbound_option, get_from_threadbound_option, DataType},
+ string::nsCString_to_OsString,
+ BitsRequest, BitsService,
+};
+
+use bits_client::{BitsClient, BitsMonitorClient, BitsProxyUsage, Guid};
+use crossbeam_utils::atomic::AtomicCell;
+use log::{info, warn};
+use moz_task::Task;
+use nserror::nsresult;
+use nsstring::nsCString;
+use xpcom::{
+ interfaces::{nsIBitsNewRequestCallback, nsIRequestObserver, nsISupports},
+ RefPtr, ThreadBoundRefPtr,
+};
+
+// D is the Data Type that the RunFn function needs to make S.
+// S is the Success Type that the RunFn returns on success and that the
+// DoneFn needs to make the BitsRequest.
+type RunFn<D, S> = fn(&D, &mut BitsClient) -> Result<S, BitsTaskError>;
+type DoneFn<D, S> = fn(
+ &D,
+ S,
+ &ClientInitData,
+ &BitsService,
+ &nsIRequestObserver,
+ Option<&nsISupports>,
+) -> Result<RefPtr<BitsRequest>, BitsTaskError>;
+
+pub struct ServiceTask<D, S> {
+ client_init_data: ClientInitData,
+ action: ServiceAction,
+ task_data: D,
+ run_fn: RunFn<D, S>,
+ done_fn: DoneFn<D, S>,
+ bits_service: AtomicCell<Option<ThreadBoundRefPtr<BitsService>>>,
+ observer: AtomicCell<Option<ThreadBoundRefPtr<nsIRequestObserver>>>,
+ context: AtomicCell<Option<ThreadBoundRefPtr<nsISupports>>>,
+ callback: AtomicCell<Option<ThreadBoundRefPtr<nsIBitsNewRequestCallback>>>,
+ result: AtomicCell<Option<Result<S, BitsTaskError>>>,
+}
+
+impl<D, S> ServiceTask<D, S>
+where
+ D: Sync + Send,
+ S: Sync + Send,
+{
+ pub fn new(
+ client_init_data: ClientInitData,
+ action: ServiceAction,
+ task_data: D,
+ run_fn: RunFn<D, S>,
+ done_fn: DoneFn<D, S>,
+ bits_service: RefPtr<BitsService>,
+ observer: RefPtr<nsIRequestObserver>,
+ context: Option<RefPtr<nsISupports>>,
+ callback: RefPtr<nsIBitsNewRequestCallback>,
+ ) -> ServiceTask<D, S> {
+ ServiceTask {
+ client_init_data,
+ action,
+ task_data,
+ run_fn,
+ done_fn,
+ bits_service: AtomicCell::new(Some(ThreadBoundRefPtr::new(bits_service))),
+ observer: AtomicCell::new(Some(ThreadBoundRefPtr::new(observer))),
+ context: AtomicCell::new(context.map(ThreadBoundRefPtr::new)),
+ callback: AtomicCell::new(Some(ThreadBoundRefPtr::new(callback))),
+ result: AtomicCell::new(None),
+ }
+ }
+}
+
+impl<D, S> Task for ServiceTask<D, S> {
+ fn run(&self) {
+ let result =
+ with_maybe_new_bits_client(&self.client_init_data, self.action.into(), |client| {
+ (self.run_fn)(&self.task_data, client)
+ });
+ self.result.store(Some(result));
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ // If TaskRunnable.run() calls Task.done() to return a result
+ // on the main thread before TaskRunnable.run() returns on the worker
+ // thread, then the Task will get dropped on the worker thread.
+ //
+ // But the callback is an nsXPCWrappedJS that isn't safe to release
+ // on the worker thread. So we move it out of the Task here to ensure
+ // it gets released on the main thread.
+ let maybe_tb_callback = self.callback.swap(None);
+ // It also isn't safe to drop the BitsService RefPtr off-thread,
+ // because BitsService refcounting is non-atomic
+ let maybe_tb_service = self.bits_service.swap(None);
+ // The observer and context are also an nsXPCWrappedJS that aren't safe
+ // to release on the worker thread.
+ let maybe_tb_observer = self.observer.swap(None);
+ let maybe_tb_context = self.context.swap(None);
+
+ let action: Action = self.action.into();
+ let maybe_callback =
+ expect_from_threadbound_option(&maybe_tb_callback, DataType::Callback, action);
+
+ // Immediately invoked function expression to allow for the ? operator
+ let result: Result<RefPtr<BitsRequest>, BitsTaskError> = (|| {
+ let bits_service =
+ expect_from_threadbound_option(&maybe_tb_service, DataType::BitsService, action)?;
+ let observer =
+ expect_from_threadbound_option(&maybe_tb_observer, DataType::Observer, action)?;
+ let maybe_context =
+ get_from_threadbound_option(&maybe_tb_context, DataType::Context, action);
+ let success = self
+ .result
+ .swap(None)
+ .ok_or_else(|| BitsTaskError::missing_result(action))??;
+
+ (self.done_fn)(
+ &self.task_data,
+ success,
+ &self.client_init_data,
+ bits_service,
+ observer,
+ maybe_context,
+ )
+ })();
+ info!("BITS Interface Task completed: {:?}", result);
+ // We incremented the request count when we dispatched an action to
+ // start the job. Now we will decrement since the action completed.
+ // See the declaration of InitBitsService::request_count for details.
+ let bits_service_result =
+ expect_from_threadbound_option(&maybe_tb_service, DataType::BitsService, action);
+ match bits_service_result {
+ Ok(bits_service) => {
+ bits_service.dec_request_count();
+ }
+ Err(error) => {
+ warn!(
+ concat!(
+ "Unable to decrement the request count when finishing ServiceTask. ",
+ "The command thread may not be shut down. Error: {:?}"
+ ),
+ error
+ );
+ }
+ }
+
+ maybe_dispatch_request_via_callback(result, maybe_callback, CallbackExpected)
+ }
+}
+
+struct StartDownloadData {
+ download_url: nsCString,
+ save_rel_path: nsCString,
+ proxy: BitsProxyUsage,
+ no_progress_timeout_secs: u32,
+ update_interval_ms: u32,
+}
+
+struct StartDownloadSuccess {
+ guid: Guid,
+ monitor_client: BitsMonitorClient,
+}
+
+pub struct StartDownloadTask(ServiceTask<StartDownloadData, StartDownloadSuccess>);
+
+impl Task for StartDownloadTask {
+ fn run(&self) {
+ self.0.run();
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ self.0.done()
+ }
+}
+
+impl StartDownloadTask {
+ pub fn new(
+ client_init_data: ClientInitData,
+ download_url: nsCString,
+ save_rel_path: nsCString,
+ proxy: BitsProxyUsage,
+ no_progress_timeout_secs: u32,
+ update_interval_ms: u32,
+ bits_service: RefPtr<BitsService>,
+ observer: RefPtr<nsIRequestObserver>,
+ context: Option<RefPtr<nsISupports>>,
+ callback: RefPtr<nsIBitsNewRequestCallback>,
+ ) -> StartDownloadTask {
+ StartDownloadTask(ServiceTask::new(
+ client_init_data,
+ ServiceAction::StartDownload,
+ StartDownloadData {
+ download_url,
+ save_rel_path,
+ proxy,
+ no_progress_timeout_secs,
+ update_interval_ms,
+ },
+ StartDownloadTask::run_fn,
+ StartDownloadTask::done_fn,
+ bits_service,
+ observer,
+ context,
+ callback,
+ ))
+ }
+
+ fn run_fn(
+ data: &StartDownloadData,
+ client: &mut BitsClient,
+ ) -> Result<StartDownloadSuccess, BitsTaskError> {
+ let url = nsCString_to_OsString(&data.download_url, StartDownload, CommandThread)?;
+ let path = nsCString_to_OsString(&data.save_rel_path, StartDownload, CommandThread)?;
+ let (success, monitor_client) = client
+ .start_job(
+ url,
+ path,
+ data.proxy,
+ data.no_progress_timeout_secs,
+ data.update_interval_ms,
+ )
+ .map_err(|pipe_error| BitsTaskError::from_pipe(StartDownload, pipe_error))??;
+ Ok(StartDownloadSuccess {
+ guid: success.guid,
+ monitor_client,
+ })
+ }
+
+ fn done_fn(
+ _data: &StartDownloadData,
+ success: StartDownloadSuccess,
+ client_init_data: &ClientInitData,
+ bits_service: &BitsService,
+ observer: &nsIRequestObserver,
+ maybe_context: Option<&nsISupports>,
+ ) -> Result<RefPtr<BitsRequest>, BitsTaskError> {
+ BitsRequest::new(
+ success.guid.clone(),
+ RefPtr::new(bits_service),
+ client_init_data.monitor_timeout_ms,
+ RefPtr::new(&observer),
+ maybe_context.map(RefPtr::new),
+ success.monitor_client,
+ ServiceAction::StartDownload,
+ )
+ }
+}
+
+struct MonitorDownloadData {
+ guid: Guid,
+ update_interval_ms: u32,
+}
+
+pub struct MonitorDownloadTask(ServiceTask<MonitorDownloadData, BitsMonitorClient>);
+
+impl Task for MonitorDownloadTask {
+ fn run(&self) {
+ self.0.run();
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ self.0.done()
+ }
+}
+
+impl MonitorDownloadTask {
+ pub fn new(
+ client_init_data: ClientInitData,
+ guid: Guid,
+ update_interval_ms: u32,
+ bits_service: RefPtr<BitsService>,
+ observer: RefPtr<nsIRequestObserver>,
+ context: Option<RefPtr<nsISupports>>,
+ callback: RefPtr<nsIBitsNewRequestCallback>,
+ ) -> MonitorDownloadTask {
+ MonitorDownloadTask(ServiceTask::new(
+ client_init_data,
+ ServiceAction::MonitorDownload,
+ MonitorDownloadData {
+ guid,
+ update_interval_ms,
+ },
+ MonitorDownloadTask::run_fn,
+ MonitorDownloadTask::done_fn,
+ bits_service,
+ observer,
+ context,
+ callback,
+ ))
+ }
+
+ fn run_fn(
+ data: &MonitorDownloadData,
+ client: &mut BitsClient,
+ ) -> Result<BitsMonitorClient, BitsTaskError> {
+ let result = client
+ .monitor_job(data.guid.clone(), data.update_interval_ms)
+ .map_err(|pipe_error| BitsTaskError::from_pipe(MonitorDownload, pipe_error));
+ Ok(result??)
+ }
+
+ fn done_fn(
+ data: &MonitorDownloadData,
+ monitor_client: BitsMonitorClient,
+ client_init_data: &ClientInitData,
+ bits_service: &BitsService,
+ observer: &nsIRequestObserver,
+ maybe_context: Option<&nsISupports>,
+ ) -> Result<RefPtr<BitsRequest>, BitsTaskError> {
+ BitsRequest::new(
+ data.guid.clone(),
+ RefPtr::new(bits_service),
+ client_init_data.monitor_timeout_ms,
+ RefPtr::new(&observer),
+ maybe_context.map(RefPtr::new),
+ monitor_client,
+ ServiceAction::MonitorDownload,
+ )
+ }
+}
diff --git a/toolkit/components/bitsdownload/src/bits_interface/xpcom_methods.rs b/toolkit/components/bitsdownload/src/bits_interface/xpcom_methods.rs
new file mode 100644
index 0000000000..cb267b9634
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/xpcom_methods.rs
@@ -0,0 +1,195 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+/// This macro is very similar to xpcom_macro, but works a bit differently:
+///
+/// When possible, it returns errors via the callback rather than via the return
+/// value.
+///
+/// It implicitly adds the callback argument of type: nsIBitsNewRequestCallback
+///
+/// It needs an action type, to be specified before the rust name, in square
+/// brackets.
+///
+/// The rustic implementation that the xpcom method calls is expected to return
+/// the type: Result<_, BitsTaskError>. If this value is Ok, it will be ignored.
+/// If the value is Err, it will be returned via the callback passed.
+///
+/// Usage like this:
+///
+/// ```ignore
+/// nsIBits_method!(
+/// [ActionType]
+/// rust_method => XpcomMethod(
+/// foo: *const nsACString,
+/// bar: *const nsIBar,
+/// baz: bool,
+/// [optional] qux: *const nsIQux,
+/// )
+/// );
+/// ```
+///
+/// Results in the macro generating a method like:
+///
+/// ```ignore
+/// unsafe fn XpcomMethod(
+/// &self,
+/// foo: *const nsACString,
+/// bar: *const nsIBar,
+/// baz: bool,
+/// qux: *const nsIQux,
+/// callback: *const nsIBitsNewRequestCallback,
+/// ) -> nsresult {
+/// let callback: &nsIBitsNewRequestCallback = match xpcom::Ensure::ensure(callback) {
+/// Ok(val) => val,
+/// Err(result) => return result,
+/// };
+/// let foo = match xpcom::Ensure::ensure(foo) {
+/// Ok(val) => val,
+/// Err(_) => {
+/// dispatch_pretask_interface_error(BitsTaskError::new(ErrorType::NullArgument, ActionType.into(), ErrorStage::Pretask), callback);
+/// return NS_OK;
+/// }
+/// };
+/// let bar = match xpcom::Ensure::ensure(bar) {
+/// Ok(val) => val,
+/// Err(_) => {
+/// dispatch_pretask_interface_error(BitsTaskError::new(ErrorType::NullArgument, ActionType.into(), ErrorStage::Pretask), callback);
+/// return NS_OK;
+/// }
+/// };
+/// let baz = match xpcom::Ensure::ensure(baz) {
+/// Ok(val) => val,
+/// Err(_) => {
+/// dispatch_pretask_interface_error(BitsTaskError::new(ErrorType::NullArgument, ActionType.into(), ErrorStage::Pretask), callback);
+/// return NS_OK;
+/// }
+/// };
+/// let qux = match xpcom::Ensure::ensure(qux) {
+/// Ok(val) => Some(val),
+/// Err(_) => None,
+/// };
+///
+/// if let Err(error) = self.rust_method(foo, bar, baz, qux, callback) {
+/// dispatch_pretask_interface_error(error, callback);
+/// }
+///
+/// NS_OK
+/// }
+/// ```
+///
+/// Which expects a Rustic implementation method like:
+///
+/// ```ignore
+/// fn rust_method(
+/// &self,
+/// foo: &nsACString,
+/// bar: &nsIBar,
+/// baz: bool,
+/// qux: Option<&nsIQux>,
+/// callback: &nsIBitsNewRequestCallback,
+/// ) -> Result<(), BitsTaskError> {
+/// do_something()
+/// }
+/// ```
+#[macro_export]
+macro_rules! nsIBits_method {
+ // The internal rule @ensure_param converts raw pointer arguments to
+ // references, calling dispatch_pretask_interface_error and returning if the
+ // argument is null.
+ // If, however, the type is optional, the reference will also be wrapped
+ // in an option and null pointers will be converted to None.
+ (@ensure_param [optional] $name:ident, $action:expr, $callback:ident) => {
+ let $name = match Ensure::ensure($name) {
+ Ok(val) => Some(val),
+ Err(_) => None,
+ };
+ };
+ (@ensure_param $name:ident, $action:expr, $callback:ident) => {
+ let $name = match Ensure::ensure($name) {
+ Ok(val) => val,
+ Err(_) => {
+ dispatch_pretask_interface_error(BitsTaskError::new(NullArgument, $action.into(), Pretask), $callback);
+ return NS_OK;
+ }
+ };
+ };
+
+ ([$action:expr] $rust_name:ident => $xpcom_name:ident($($([$param_required:ident])* $param_name:ident: $param_type:ty $(,)*)*)) => {
+ #[allow(non_snake_case)]
+ unsafe fn $xpcom_name(&self, $($param_name: $param_type, )* callback: *const nsIBitsNewRequestCallback) -> nsresult {
+ use xpcom::Ensure;
+ use nserror::NS_OK;
+ // When no params are passed, the imports below will not be used, so silence the
+ // warning
+ #[allow(unused_imports)]
+ use bits_interface::{
+ dispatch_callback::dispatch_pretask_interface_error,
+ error::{BitsTaskError, ErrorStage::Pretask, ErrorType::NullArgument},
+ };
+
+ let callback: &nsIBitsNewRequestCallback = match Ensure::ensure(callback) {
+ Ok(val) => val,
+ Err(result) => return result,
+ };
+ $(nsIBits_method!(@ensure_param $([$param_required])* $param_name, $action, callback);)*
+ if let Err(error) = self.$rust_name($($param_name, )* callback) {
+ dispatch_pretask_interface_error(error, callback);
+ }
+ NS_OK
+ }
+ };
+}
+
+/*
+ * Same as above, but expects a nsIBitsCallback as its callback.
+ */
+#[macro_export]
+macro_rules! nsIBitsRequest_method {
+ // The internal rule @ensure_param converts raw pointer arguments to
+ // references, calling dispatch_pretask_interface_error and returning if the
+ // argument is null.
+ // If, however, the type is optional, the reference will also be wrapped
+ // in an option and null pointers will be converted to None.
+ (@ensure_param [optional] $name:ident, $action:expr, $callback:ident) => {
+ let $name = match Ensure::ensure($name) {
+ Ok(val) => Some(val),
+ Err(_) => None,
+ };
+ };
+ (@ensure_param $name:ident, $action:expr, $callback:ident) => {
+ let $name = match Ensure::ensure($name) {
+ Ok(val) => val,
+ Err(_) => {
+ dispatch_pretask_request_error(BitsTaskError::new(NullArgument, $action.into(), Pretask), $callback);
+ return NS_OK;
+ }
+ };
+ };
+
+ ([$action:expr] $rust_name:ident => $xpcom_name:ident($($([$param_required:ident])* $param_name:ident: $param_type:ty $(,)*)*)) => {
+ #[allow(non_snake_case)]
+ unsafe fn $xpcom_name(&self, $($param_name: $param_type, )* callback: *const nsIBitsCallback) -> nsresult {
+ use xpcom::Ensure;
+ use nserror::NS_OK;
+ // When no params are passed, the imports below will not be used, so silence the
+ // warning
+ #[allow(unused_imports)]
+ use bits_interface::{
+ dispatch_callback::dispatch_pretask_request_error,
+ error::{BitsTaskError, ErrorStage::Pretask, ErrorType::NullArgument},
+ };
+
+ let callback: &nsIBitsCallback = match Ensure::ensure(callback) {
+ Ok(val) => val,
+ Err(result) => return result,
+ };
+ $(nsIBitsRequest_method!(@ensure_param $([$param_required])* $param_name, $action, callback);)*
+ if let Err(error) = self.$rust_name($($param_name, )* callback) {
+ dispatch_pretask_request_error(error, callback);
+ }
+ NS_OK
+ }
+ };
+}
diff --git a/toolkit/components/bitsdownload/src/lib.rs b/toolkit/components/bitsdownload/src/lib.rs
new file mode 100644
index 0000000000..3b8ef021da
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/lib.rs
@@ -0,0 +1,23 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+//! This crate is meant to be used in Windows only. It provides the
+//! bits_interface module, which implements the nsIBits an nsIBitsRequest
+//! XPCOM interfaces. These interfaces allow usage of the Windows component:
+//! BITS (Background Intelligent Transfer Service). Further documentation can
+//! be found in the XPCOM interface definition, located in nsIBits.idl
+
+#![cfg(target_os = "windows")]
+
+extern crate bits_client;
+extern crate comedy;
+extern crate crossbeam_utils;
+extern crate libc;
+extern crate log;
+extern crate moz_task;
+extern crate nserror;
+extern crate nsstring;
+extern crate xpcom;
+
+pub mod bits_interface;