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