use super::device_storage::DeviceStorage;
use super::fidl_storage::FidlStorage;
use crate::private::Sealed;
use crate::stash_logger::StashInspectLogger;
use anyhow::{format_err, Error};
use fidl::endpoints::create_proxy;
use fidl_fuchsia_io::DirectoryProxy;
use fidl_fuchsia_stash::StoreProxy;
use futures::lock::Mutex;
use futures::Future;
use std::any::Any;
use std::collections::HashMap;
use std::rc::Rc;
pub trait DefaultLoader {
type Result;
fn default_value(&self) -> Self::Result;
}
impl<T> Sealed for T where T: DefaultLoader {}
pub struct NoneT;
impl Sealed for NoneT {}
pub trait StorageFactory {
type Storage;
fn initialize<T>(&self) -> impl Future<Output = Result<(), Error>>
where
T: StorageAccess<Storage = Self::Storage>;
fn initialize_with_loader<T, L>(&self, loader: L) -> impl Future<Output = Result<(), Error>>
where
T: StorageAccess<Storage = Self::Storage>,
L: DefaultLoader<Result = T::Data> + 'static;
fn get_store(&self) -> impl Future<Output = Rc<Self::Storage>>;
}
pub trait StorageAccess {
type Storage;
type Data;
const STORAGE_KEY: &'static str;
}
pub enum InitializationState<T, U = ()> {
Initializing(HashMap<&'static str, Option<Box<dyn Any>>>, U),
Partial,
Initialized(Rc<T>),
}
impl<T> InitializationState<T, ()> {
pub fn new() -> Self {
Self::Initializing(HashMap::new(), ())
}
}
impl<T, U> Default for InitializationState<T, U>
where
U: Default,
{
fn default() -> Self {
Self::Initializing(Default::default(), Default::default())
}
}
impl<T> InitializationState<T, DirectoryProxy> {
pub fn with_storage_dir(storage_dir: DirectoryProxy) -> Self {
Self::Initializing(HashMap::new(), storage_dir)
}
}
pub struct StashDeviceStorageFactory {
store: StoreProxy,
device_storage_cache: Mutex<InitializationState<DeviceStorage>>,
inspect_handle: Rc<Mutex<StashInspectLogger>>,
}
impl StashDeviceStorageFactory {
pub fn new(
store: StoreProxy,
inspect_handle: Rc<Mutex<StashInspectLogger>>,
) -> StashDeviceStorageFactory {
StashDeviceStorageFactory {
store,
device_storage_cache: Mutex::new(InitializationState::new()),
inspect_handle,
}
}
async fn initialize_storage(&self, key: &'static str) -> Result<(), Error> {
match &mut *self.device_storage_cache.lock().await {
InitializationState::Initializing(initial_keys, ()) => {
let _ = initial_keys.insert(key, None);
Ok(())
}
InitializationState::Initialized(_) => {
Err(format_err!("Cannot initialize an already accessed device storage"))
}
_ => unreachable!(),
}
}
async fn initialize_storage_with_loader(
&self,
key: &'static str,
loader: Box<dyn Any>,
) -> Result<(), Error> {
match &mut *self.device_storage_cache.lock().await {
InitializationState::Initializing(initial_keys, ()) => {
let _ = initial_keys.insert(key, Some(loader));
Ok(())
}
InitializationState::Initialized(_) => {
Err(format_err!("Cannot initialize an already accessed device storage"))
}
_ => unreachable!(),
}
}
}
impl StorageFactory for StashDeviceStorageFactory {
type Storage = DeviceStorage;
async fn initialize<T>(&self) -> Result<(), Error>
where
T: StorageAccess<Storage = DeviceStorage>,
{
self.initialize_storage(T::STORAGE_KEY).await
}
async fn initialize_with_loader<T, L>(&self, loader: L) -> Result<(), Error>
where
T: StorageAccess<Storage = DeviceStorage>,
L: DefaultLoader<Result = T::Data> + 'static,
{
self.initialize_storage_with_loader(T::STORAGE_KEY, Box::new(loader) as Box<dyn Any>).await
}
async fn get_store(&self) -> Rc<DeviceStorage> {
let initialization = &mut *self.device_storage_cache.lock().await;
match initialization {
InitializationState::Initializing(initial_keys, ()) => {
let device_storage = Rc::new(DeviceStorage::with_stash_proxy(
initial_keys.drain(),
|| {
let (accessor_proxy, server_end) = create_proxy();
self.store
.create_accessor(false, server_end)
.expect("failed to create accessor for stash");
accessor_proxy
},
Rc::clone(&self.inspect_handle),
));
*initialization = InitializationState::Initialized(Rc::clone(&device_storage));
device_storage
}
InitializationState::Initialized(device_storage) => Rc::clone(device_storage),
_ => unreachable!(),
}
}
}
pub struct FidlStorageFactory {
migration_id: u64,
device_storage_cache: Mutex<InitializationState<FidlStorage, DirectoryProxy>>,
}
impl FidlStorageFactory {
pub fn new(migration_id: u64, storage_dir: DirectoryProxy) -> FidlStorageFactory {
FidlStorageFactory {
migration_id,
device_storage_cache: Mutex::new(InitializationState::with_storage_dir(storage_dir)),
}
}
async fn initialize_storage(&self, key: &'static str) -> Result<(), Error> {
match &mut *self.device_storage_cache.lock().await {
InitializationState::Initializing(initial_keys, _) => {
let _ = initial_keys.insert(key, None);
Ok(())
}
InitializationState::Initialized(_) => {
Err(format_err!("Cannot initialize an already accessed device storage"))
}
_ => unreachable!(),
}
}
async fn initialize_storage_with_loader(
&self,
key: &'static str,
loader: Box<dyn Any>,
) -> Result<(), Error> {
match &mut *self.device_storage_cache.lock().await {
InitializationState::Initializing(initial_keys, _) => {
let _ = initial_keys.insert(key, Some(loader));
Ok(())
}
InitializationState::Initialized(_) => {
Err(format_err!("Cannot initialize an already accessed device storage"))
}
_ => unreachable!(),
}
}
}
impl StorageFactory for FidlStorageFactory {
type Storage = FidlStorage;
async fn initialize<T>(&self) -> Result<(), Error>
where
T: StorageAccess<Storage = FidlStorage>,
{
self.initialize_storage(T::STORAGE_KEY).await
}
async fn initialize_with_loader<T, L>(&self, loader: L) -> Result<(), Error>
where
T: StorageAccess<Storage = FidlStorage>,
L: DefaultLoader<Result = T::Data> + 'static,
{
self.initialize_storage_with_loader(T::STORAGE_KEY, Box::new(loader) as Box<dyn Any>).await
}
async fn get_store(&self) -> Rc<FidlStorage> {
let initialization = &mut *self.device_storage_cache.lock().await;
match initialization {
InitializationState::Initializing(..) => {
let (initial_keys, storage_dir) =
match std::mem::replace(initialization, InitializationState::Partial) {
InitializationState::Initializing(initial_keys, storage_dir) => {
(initial_keys, storage_dir)
}
_ => unreachable!(),
};
let migration_id = self.migration_id;
let (device_storage, sync_tasks) =
FidlStorage::with_file_proxy(initial_keys, storage_dir, move |key| {
let temp_file_name = format!("{key}.tmp");
let file_name = format!("{key}_{migration_id}.pfidl");
Ok((temp_file_name, file_name))
})
.await
.expect("failed to get storage");
for task in sync_tasks {
task.detach();
}
let device_storage = Rc::new(device_storage);
*initialization = InitializationState::Initialized(Rc::clone(&device_storage));
device_storage
}
InitializationState::Initialized(device_storage) => Rc::clone(device_storage),
_ => unreachable!(),
}
}
}