summaryrefslogtreecommitdiffstats
path: root/toolkit/components/bitsdownload/src/bits_interface/request.rs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/bitsdownload/src/bits_interface/request.rs')
-rw-r--r--toolkit/components/bitsdownload/src/bits_interface/request.rs763
1 files changed, 763 insertions, 0 deletions
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();
+ }
+}