/* 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/. */ //! Component interface implementation. //! //! This module implements the `nsIKeyValue` XPCOM interfaces that are //! exposed to C++ and chrome JS callers. use std::{io, ops::Bound, sync::Arc}; use atomic_refcell::AtomicRefCell; use crossbeam_utils::atomic::AtomicCell; use nserror::{nsresult, NS_OK}; use nsstring::{nsACString, nsAString, nsCString, nsString}; use rkv::{ backend::{SafeMode as RkvSafeMode, SafeModeEnvironment as RkvSafeModeEnvironment}, Rkv, }; use storage_variant::{HashPropertyBag, VariantType}; use thin_vec::ThinVec; use xpcom::{ getter_addrefs, interfaces::{ nsIAsyncShutdownClient, nsIAsyncShutdownService, nsIKeyValueDatabaseCallback, nsIKeyValueDatabaseImportOptions, nsIKeyValueEnumeratorCallback, nsIKeyValueImportSourceSpec, nsIKeyValueImporter, nsIKeyValuePair, nsIKeyValueVariantCallback, nsIKeyValueVoidCallback, nsIPropertyBag, nsIVariant, }, xpcom, xpcom_method, RefPtr, }; use crate::{ fs::WidePathBuf, skv::{ abort::AbortError, coordinator::{Coordinator, CoordinatorClient, CoordinatorError}, database::{Database, DatabaseError, GetOptions}, importer::{ CleanupPolicy, ConflictPolicy, ImporterError, NamedSourceDatabase, RkvImporter, SourceDatabases, }, key::Key, store::{Store, StoreError, StorePath}, value::{Value, ValueError}, }, }; #[xpcom(implement(nsIKeyValueService), atomic)] pub struct KeyValueService { client: CoordinatorClient<'static>, } impl KeyValueService { pub fn new() -> RefPtr { let client = Coordinator::get_or_create().client_with_name("skv:KeyValueService"); KeyValueServiceShutdownBlocker::register(client.clone()); KeyValueService::allocate(InitKeyValueService { client }) } xpcom_method!( get_or_create => GetOrCreate( callback: *const nsIKeyValueDatabaseCallback, dir: *const nsAString, name: *const nsACString ) ); fn get_or_create( &self, callback: &nsIKeyValueDatabaseCallback, dir: &nsAString, name: &nsACString, ) -> Result<(), Infallible> { let client = self.client.clone(); let dir = WidePathBuf::new(dir); let request = moz_task::spawn_blocking("skv:KeyValueService:GetOrCreate:Request", async move { Ok(( client.child_with_name("skv:KeyValueDatabase")?, StorePath::canonicalizing(dir)?, )) }); let name = nsCString::from(name); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueService:GetOrCreate:Response", async move { match request.await { Ok((client, path)) => { let db = KeyValueDatabase::new(client, path, name.to_utf8().into()); unsafe { callback.Resolve(db.coerce()) } } Err::<_, InterfaceError>(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( get_or_create_with_options => GetOrCreateWithOptions( callback: *const nsIKeyValueDatabaseCallback, path: *const nsAString, name: *const nsACString, strategy: u8 ) ); fn get_or_create_with_options( &self, _callback: &nsIKeyValueDatabaseCallback, _path: &nsAString, _name: &nsACString, _strategy: u8, ) -> Result<(), nsresult> { Err(nserror::NS_ERROR_NOT_IMPLEMENTED) } xpcom_method!( create_importer => CreateImporter( type_: *const nsACString, dir: *const nsAString ) -> *const nsIKeyValueImporter ); fn create_importer( &self, type_: &nsACString, dir: &nsAString, ) -> Result, nsresult> { match &*type_.to_utf8() { RkvSafeModeKeyValueImporter::TYPE => { let client = self .client .child_with_name("skv:RkvSafeModeKeyValueImporter") .map_err(|_| nserror::NS_ERROR_FAILURE)?; let importer = RkvSafeModeKeyValueImporter::new(client, WidePathBuf::new(dir)); Ok(RefPtr::new(importer.coerce())) } _ => Err(nserror::NS_ERROR_INVALID_ARG), } } } #[xpcom(implement(nsIKeyValueDatabase), atomic)] pub struct KeyValueDatabase { client: CoordinatorClient<'static>, path: StorePath, name: String, } impl KeyValueDatabase { fn new(client: CoordinatorClient<'static>, path: StorePath, name: String) -> RefPtr { KeyValueDatabase::allocate(InitKeyValueDatabase { client, path, name }) } fn store(&self) -> Result, InterfaceError> { Ok(self.client.store_for_path(self.path.clone())?) } xpcom_method!( is_empty => IsEmpty( callback: *const nsIKeyValueVariantCallback ) ); fn is_empty(&self, callback: &nsIKeyValueVariantCallback) -> Result<(), Infallible> { let store = self.store(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:IsEmpty:Request", async move { let store = store?; let db = Database::new(&store, &name); Ok(db.is_empty()?) }); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:IsEmpty:Response", async move { match signal.aborting(request).await { Ok(b) => unsafe { callback.Resolve(b.into_variant().coerce()) }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("isEmpty: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( count => Count( callback: *const nsIKeyValueVariantCallback ) ); fn count(&self, callback: &nsIKeyValueVariantCallback) -> Result<(), Infallible> { let store = self.store(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Count:Request", async move { let store = store?; let db = Database::new(&store, &name); Ok(db.count()?) }); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:Count:Response", async move { match signal.aborting(request).await { Ok(n) => unsafe { callback.Resolve(n.into_variant().coerce()) }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("count: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( size => Size( callback: *const nsIKeyValueVariantCallback ) ); fn size(&self, callback: &nsIKeyValueVariantCallback) -> Result<(), Infallible> { let store = self.store(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Size:Request", async move { let store = store?; let db = Database::new(&store, &name); Ok(db.size()?) }); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:Size:Response", async move { match signal.aborting(request).await { Ok(n) => unsafe { callback.Resolve(n.into_variant().coerce()) }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("size: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( put => Put( callback: *const nsIKeyValueVoidCallback, key: *const nsACString, value: *const nsIVariant ) ); fn put( &self, callback: &nsIKeyValueVoidCallback, key: &nsACString, value: &nsIVariant, ) -> Result<(), Infallible> { let inputs = || -> Result<_, InterfaceError> { let store = self.store()?; let key = Key::from(key); let value = Value::from_variant(value)?; Ok((store, key, value)) }(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Put:Request", async move { let (store, key, value) = inputs?; let db = Database::new(&store, &name); Ok(db.put(&[(key, value)])?) }); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:Put:Response", async move { match signal.aborting(request).await { Ok(()) => unsafe { callback.Resolve() }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("put: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( write_many => WriteMany( callback: *const nsIKeyValueVoidCallback, pairs: *const ThinVec>> ) ); fn write_many( &self, callback: &nsIKeyValueVoidCallback, pairs: &ThinVec>>, ) -> Result<(), Infallible> { let inputs = || -> Result<_, InterfaceError> { let store = self.store()?; let pairs = pairs .into_iter() .flatten() .map(|pair| { let mut key = nsCString::new(); unsafe { pair.GetKey(&mut *key) }.to_result()?; let value = getter_addrefs(|p| unsafe { pair.GetValue(p) })?; Ok((Key::from(&*key), Value::from_variant(value.coerce())?)) }) .collect::, InterfaceError>>()?; Ok((store, pairs)) }(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:WriteMany:Request", async move { let (store, pairs) = inputs?; let db = Database::new(&store, &name); Ok(db.put(pairs.as_slice())?) }); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:WriteMany:Response", async move { match signal.aborting(request).await { Ok(()) => unsafe { callback.Resolve() }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("writeMany: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( get => Get( callback: *const nsIKeyValueVariantCallback, key: *const nsACString, default_value: *const nsIVariant ) ); fn get( &self, callback: &nsIKeyValueVariantCallback, key: &nsACString, default_value: &nsIVariant, ) -> Result<(), Infallible> { let inputs = || -> Result<_, InterfaceError> { let store = self.store()?; let key = Key::from(key); Ok((store, key)) }(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Get:Request", async move { let (store, key) = inputs?; let db = Database::new(&store, &name); Ok(db.get(&key, &GetOptions::new())?) }); let signal = self.client.signal(); let default_value = RefPtr::new(default_value); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:Get:Response", async move { let result = signal.aborting(request).await.and_then(|value| { Ok(match value { Some(value) => value.to_variant()?, None => default_value, }) }); match result { Ok(variant) => unsafe { callback.Resolve(variant.coerce()) }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("get: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( has => Has(callback: *const nsIKeyValueVariantCallback, key: *const nsACString) ); fn has( &self, callback: &nsIKeyValueVariantCallback, key: &nsACString, ) -> Result<(), Infallible> { let inputs = || -> Result<_, InterfaceError> { let store = self.store()?; let key = Key::from(key); Ok((store, key)) }(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Has:Request", async move { let (store, key) = inputs?; let db = Database::new(&store, &name); Ok(db.has(&key, &GetOptions::new())?) }); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:Has:Response", async move { match signal.aborting(request).await { Ok(b) => unsafe { callback.Resolve(b.into_variant().coerce()) }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("has: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( delete => Delete(callback: *const nsIKeyValueVoidCallback, key: *const nsACString) ); fn delete( &self, callback: &nsIKeyValueVoidCallback, key: &nsACString, ) -> Result<(), Infallible> { let inputs = || -> Result<_, InterfaceError> { let store = self.store()?; let key = Key::from(key); Ok((store, key)) }(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Delete:Request", async move { let (store, key) = inputs?; let db = Database::new(&store, &name); Ok(db.delete(&key)?) }); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:Delete:Response", async move { match signal.aborting(request).await { Ok(()) => unsafe { callback.Resolve() }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("delete: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( delete_range => DeleteRange( callback: *const nsIKeyValueVoidCallback, from_key: *const nsACString, to_key: *const nsACString ) ); fn delete_range( &self, callback: &nsIKeyValueVoidCallback, from_key: &nsACString, to_key: &nsACString, ) -> Result<(), Infallible> { let inputs = || -> Result<_, InterfaceError> { let store = self.store()?; let from_key = match from_key.is_empty() { true => Bound::Unbounded, false => Bound::Included(Key::from(from_key)), }; let to_key = match to_key.is_empty() { true => Bound::Unbounded, false => Bound::Excluded(Key::from(to_key)), }; Ok((store, from_key, to_key)) }(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:DeleteRange:Request", async move { let (store, from_key, to_key) = inputs?; let db = Database::new(&store, &name); Ok(db.delete_range((from_key, to_key))?) }); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:DeleteRange:Response", async move { match signal.aborting(request).await { Ok(()) => unsafe { callback.Resolve() }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("deleteRange: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( clear => Clear(callback: *const nsIKeyValueVoidCallback) ); fn clear(&self, callback: &nsIKeyValueVoidCallback) -> Result<(), Infallible> { let store = self.store(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Clear:Request", async move { let store = store?; let db = Database::new(&store, &name); Ok(db.clear()?) }); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:Clear:Response", async move { match signal.aborting(request).await { Ok(()) => unsafe { callback.Resolve() }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("clear: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( enumerate => Enumerate( callback: *const nsIKeyValueEnumeratorCallback, from_key: *const nsACString, to_key: *const nsACString ) ); fn enumerate( &self, callback: &nsIKeyValueEnumeratorCallback, from_key: &nsACString, to_key: &nsACString, ) -> Result<(), Infallible> { let inputs = || -> Result<_, InterfaceError> { let store = self.store()?; let from_key = match from_key.is_empty() { true => Bound::Unbounded, false => Bound::Included(Key::from(from_key)), }; let to_key = match to_key.is_empty() { true => Bound::Unbounded, false => Bound::Excluded(Key::from(to_key)), }; Ok((store, from_key, to_key)) }(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Enumerate:Request", async move { let (store, from_key, to_key) = inputs?; let db = Database::new(&store, &name); Ok(db.enumerate((from_key, to_key), GetOptions::new().concurrent(true))?) }); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:Enumerate:Response", async move { match signal.aborting(request).await { Ok(pairs) => { let enumerator = KeyValueEnumerator::new(pairs); unsafe { callback.Resolve(enumerator.coerce()) } } Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("enumerate: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }) .detach(); Ok(()) } xpcom_method!( close => Close( callback: *const nsIKeyValueVoidCallback ) ); fn close(&self, callback: &nsIKeyValueVoidCallback) -> Result<(), Infallible> { let client = self.client.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Close:Request", async move { client.invalidate() }); let callback = RefPtr::new(callback); moz_task::spawn_local("skv:KeyValueDatabase:Close:Response", async move { request.await; unsafe { callback.Resolve() }; }) .detach(); Ok(()) } } #[xpcom(implement(nsIKeyValueEnumerator), atomic)] pub struct KeyValueEnumerator { iter: AtomicRefCell>, } impl KeyValueEnumerator { fn new(pairs: Vec<(Key, Value)>) -> RefPtr { KeyValueEnumerator::allocate(InitKeyValueEnumerator { iter: AtomicRefCell::new(pairs.into_iter()), }) } xpcom_method!(has_more_elements => HasMoreElements() -> bool); fn has_more_elements(&self) -> Result { Ok(!self.iter.borrow().as_slice().is_empty()) } xpcom_method!(get_next => GetNext() -> *const nsIKeyValuePair); fn get_next(&self) -> Result, nsresult> { let mut iter = self.iter.borrow_mut(); let (key, value) = iter.next().ok_or(nserror::NS_ERROR_FAILURE)?; let pair = KeyValuePair::new(key, value); Ok(RefPtr::new(pair.coerce())) } } #[xpcom(implement(nsIKeyValuePair), atomic)] pub struct KeyValuePair { key: Key, value: Value, } impl KeyValuePair { fn new(key: Key, value: Value) -> RefPtr { KeyValuePair::allocate(InitKeyValuePair { key, value }) } xpcom_method!(get_key => GetKey() -> nsACString); fn get_key(&self) -> Result { Ok(self.key.clone().into()) } xpcom_method!(get_value => GetValue() -> *const nsIVariant); fn get_value(&self) -> Result, nsresult> { self.value .to_variant() .map_err(|_| nserror::NS_ERROR_FAILURE) } } #[xpcom(implement(nsIAsyncShutdownBlocker), nonatomic)] pub struct KeyValueServiceShutdownBlocker { client: CoordinatorClient<'static>, } impl KeyValueServiceShutdownBlocker { fn new(client: CoordinatorClient<'static>) -> RefPtr { assert!(moz_task::is_main_thread()); KeyValueServiceShutdownBlocker::allocate(InitKeyValueServiceShutdownBlocker { client }) } /// Registers a blocker to close all open databases on shutdown. /// /// This function may be called on any thread, but the blocker itself is /// registered on the main thread, because `nsIAsyncShutdownService` is /// main-thread-only. fn register(client: CoordinatorClient<'static>) { let Ok(main_thread) = moz_task::get_main_thread() else { return; }; moz_task::spawn_onto( "skv:KeyValueServiceShutdownBlocker:Register", main_thread.coerce(), async move { let _ = || -> Result<(), InterfaceError> { let service = xpcom::components::AsyncShutdown::service::()?; let barrier = getter_addrefs(|p| unsafe { service.GetProfileBeforeChange(p) })?; let blocker = Self::new(client); unsafe { barrier.AddBlocker( blocker.coerce(), &*nsString::from(file!()), line!() as i32, &*nsString::new(), ) } .to_result()?; Ok(()) }(); }, ) .detach(); } xpcom_method!( block_shutdown => BlockShutdown( barrier: *const nsIAsyncShutdownClient ) ); fn block_shutdown(&self, barrier: &nsIAsyncShutdownClient) -> Result<(), Infallible> { assert!(moz_task::is_main_thread()); let client = self.client.clone(); let request = moz_task::spawn_blocking( "skv:KeyValueServiceShutdownBlocker:BlockShutdown:Request", async move { client.invalidate() }, ); let barrier = RefPtr::new(barrier); let blocker = RefPtr::new(self); moz_task::spawn_local( "skv:KeyValueServiceShutdownBlocker:BlockShutdown:Response", async move { request.await; unsafe { barrier.RemoveBlocker(blocker.coerce()) }; }, ) .detach(); Ok(()) } xpcom_method!( get_name => GetName() -> nsAString ); fn get_name(&self) -> Result { Ok("KeyValueService: shutdown".into()) } xpcom_method!( get_state => GetState() -> *const nsIPropertyBag ); fn get_state(&self) -> Result, Infallible> { let state = HashPropertyBag::new(); Ok(RefPtr::new(state.bag().coerce())) } } #[xpcom(implement(nsIKeyValueImporter), atomic)] pub struct RkvSafeModeKeyValueImporter { client: CoordinatorClient<'static>, specs: AtomicRefCell>>, } impl RkvSafeModeKeyValueImporter { const TYPE: &'static str = "rkv-safe-mode"; pub fn new(client: CoordinatorClient<'static>, dir: WidePathBuf) -> RefPtr { RkvSafeModeKeyValueImporter::allocate(InitRkvSafeModeKeyValueImporter { client, // The first spec is always the destination storage directory. specs: vec![KeyValueImportSourceSpec::new(dir)].into(), }) } xpcom_method!(get_type => GetType() -> nsACString); fn get_type(&self) -> Result { Ok(Self::TYPE.into()) } xpcom_method!(get_path => GetPath() -> nsAString); fn get_path(&self) -> Result { self.specs.borrow()[0].get_path() } xpcom_method!(add_path => AddPath(dir: *const nsAString) -> *const nsIKeyValueImportSourceSpec); fn add_path(&self, dir: &nsAString) -> Result, Infallible> { let spec = KeyValueImportSourceSpec::new(WidePathBuf::new(dir)); self.specs.borrow_mut().push(spec.clone()); Ok(RefPtr::new(spec.coerce())) } xpcom_method!( add_database => AddDatabase( name: *const nsACString ) -> *const nsIKeyValueDatabaseImportOptions ); fn add_database( &self, name: &nsACString, ) -> Result, nsresult> { self.specs.borrow()[0].add_database(name) } xpcom_method!(add_all_databases => AddAllDatabases() -> *const nsIKeyValueDatabaseImportOptions); fn add_all_databases(&self) -> Result, nsresult> { self.specs.borrow()[0].add_all_databases() } xpcom_method!( import => Import( callback: *const nsIKeyValueVoidCallback ) ); fn import(&self, callback: &nsIKeyValueVoidCallback) -> Result<(), Infallible> { let client = self.client.clone(); let specs = self.specs.borrow().clone(); let request = moz_task::spawn_blocking( "skv:RkvSafeModeKeyValueImporter:Import:Request", async move { let mut manager = rkv::Manager::::singleton() .write() .unwrap(); let store = client.store_for_path(StorePath::canonicalizing(specs[0].dir.clone())?)?; // An Rkv environment is unique to a storage directory; // if we're importing from multiple directories, we need to // open an environment for each directory. let rkvs = { let mut rkvs = Vec::with_capacity(specs.len()); for spec in specs { let Some(dbs) = spec.to_source_databases() else { continue; }; let dir = spec.dir.canonicalize()?; let builder = Rkv::environment_builder::(); let rkv = manager.get_or_create_from_builder( dir.as_path(), builder, Rkv::from_builder::, )?; rkvs.push((rkv, dbs)); } rkvs }; // Import all databases from each directory, in order. for (rkv, dbs) in rkvs { let env = rkv.read().unwrap(); let importer = RkvImporter::new(&*env, &store, dbs)?; importer.import()?; importer.cleanup(); } Ok(()) }, ); let signal = self.client.signal(); let callback = RefPtr::new(callback); moz_task::spawn_local( "skv:RkvSafeModeKeyValueImporter:Import:Response", async move { match signal.aborting(request).await { Ok(()) => unsafe { callback.Resolve() }, Err(InterfaceError::Abort(_)) => unsafe { callback.Reject(&*nsCString::from("import: aborted")) }, Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) }, } }, ) .detach(); Ok(()) } } #[derive(Clone)] enum KeyValueImportSource { Databases(Vec<(String, RefPtr)>), AllDatabases(RefPtr), } #[xpcom(implement(nsIKeyValueImportSourceSpec), atomic)] pub struct KeyValueImportSourceSpec { dir: WidePathBuf, source: AtomicRefCell>, } impl KeyValueImportSourceSpec { fn new(dir: WidePathBuf) -> RefPtr { KeyValueImportSourceSpec::allocate(InitKeyValueImportSourceSpec { dir, source: AtomicRefCell::default(), }) } fn to_source_databases(&self) -> Option { self.source.borrow().as_ref().map(|source| match source { KeyValueImportSource::Databases(dbs) => SourceDatabases::Named( dbs.iter() .map(|(name, options)| NamedSourceDatabase { name: name.clone().into(), conflict_policy: options.conflict_policy.load(), cleanup_policy: options.cleanup_policy.load(), }) .collect(), ), KeyValueImportSource::AllDatabases(options) => SourceDatabases::All { conflict_policy: options.conflict_policy.load(), cleanup_policy: options.cleanup_policy.load(), }, }) } xpcom_method!(get_path => GetPath() -> nsAString); fn get_path(&self) -> Result { Ok(self.dir.as_wide().into()) } xpcom_method!( add_database => AddDatabase( dir: *const nsACString ) -> *const nsIKeyValueDatabaseImportOptions ); fn add_database( &self, name: &nsACString, ) -> Result, nsresult> { let mut guard = self.source.borrow_mut(); let source = guard.get_or_insert_with(|| KeyValueImportSource::Databases(Vec::new())); let options = match source { KeyValueImportSource::Databases(dbs) => { let options = KeyValueDatabaseImportOptions::new(); dbs.push((name.to_utf8().into_owned(), options.clone())); options } KeyValueImportSource::AllDatabases(_) => { return Err(nserror::NS_ERROR_ALREADY_INITIALIZED) } }; Ok(RefPtr::new(options.coerce())) } xpcom_method!(add_all_databases => AddAllDatabases() -> *const nsIKeyValueDatabaseImportOptions); fn add_all_databases(&self) -> Result, nsresult> { let mut guard = self.source.borrow_mut(); let options = match &mut *guard { Some(_) => return Err(nserror::NS_ERROR_ALREADY_INITIALIZED), None => { let options = KeyValueDatabaseImportOptions::new(); *guard = Some(KeyValueImportSource::AllDatabases(options.clone())); options } }; Ok(RefPtr::new(options.coerce())) } } #[xpcom(implement(nsIKeyValueDatabaseImportOptions), atomic)] pub struct KeyValueDatabaseImportOptions { conflict_policy: AtomicCell, cleanup_policy: AtomicCell, } impl KeyValueDatabaseImportOptions { fn new() -> RefPtr { KeyValueDatabaseImportOptions::allocate(InitKeyValueDatabaseImportOptions { conflict_policy: AtomicCell::new(ConflictPolicy::Error), cleanup_policy: AtomicCell::new(CleanupPolicy::Keep), }) } xpcom_method!( set_conflict_policy => SetConflictPolicy( policy: u8 ) -> *const nsIKeyValueDatabaseImportOptions ); fn set_conflict_policy( &self, policy: u8, ) -> Result, nsresult> { let policy = ConflictPolicy::try_from(policy).map_err(|_| nserror::NS_ERROR_INVALID_ARG)?; self.conflict_policy.store(policy); Ok(RefPtr::new(self.coerce())) } xpcom_method!( set_cleanup_policy => SetCleanupPolicy( policy: u8 ) -> *const nsIKeyValueDatabaseImportOptions ); fn set_cleanup_policy( &self, policy: u8, ) -> Result, nsresult> { let policy = CleanupPolicy::try_from(policy).map_err(|_| nserror::NS_ERROR_INVALID_ARG)?; self.cleanup_policy.store(policy); Ok(RefPtr::new(self.coerce())) } } /// The error type for interface methods that never return an error. /// /// This is equivalent to [`std::convert::Infallible`], but implements /// `Into`, which we can't implement on [`std::convert::Infallible`] /// because of the orphan rule. enum Infallible {} impl From for nsresult { fn from(_: Infallible) -> Self { nserror::NS_ERROR_FAILURE } } #[derive(Debug, thiserror::Error)] pub enum InterfaceError { #[error("coordinator: {0}")] Coordinator(#[from] CoordinatorError), #[error(transparent)] Abort(#[from] AbortError), #[error("database: {0}")] Database(#[from] DatabaseError), #[error("store: {0}")] Store(#[from] StoreError), #[error("value: {0}")] Value(#[from] ValueError), #[error("rkv store: {0}")] RkvStore(#[from] rkv::StoreError), #[error("importer: {0}")] Importer(#[from] ImporterError), #[error("I/O: {0}")] Io(#[from] io::Error), #[error("error code: {0}")] Nsresult(#[from] nsresult), }