use std::{
collections::{BTreeMap, btree_map::Entry},
mem::transmute,
ops::Deref,
path::{Path, PathBuf},
sync::Arc,
};
use anyhow::{Result, anyhow};
use event_listener::Event;
use hreads::on_main;
use parking_lot::{Mutex, RwLockReadGuard, RwLockWriteGuard};
use crate::{
Own, Weak,
manage::{DataStorage, Managed},
};
pub type InFlightDownloads = Mutex<BTreeMap<String, Arc<Event>>>;
fn insert_or_existing<T: Managed>(
mut storage: RwLockWriteGuard<'static, DataStorage<T>>,
name: String,
new: Own<T>,
) -> Weak<T> {
match storage.entry(name) {
Entry::Occupied(existing) => {
let existing = existing.get().weak();
drop(storage);
on_main(move || drop(new));
existing
}
Entry::Vacant(slot) => slot.insert(new).weak(),
}
}
pub trait DataManager<T: Managed> {
fn root_path() -> &'static Path;
fn set_root_path(path: impl Into<PathBuf>);
fn storage() -> RwLockReadGuard<'static, DataStorage<T>>;
fn storage_mut() -> RwLockWriteGuard<'static, DataStorage<T>>;
fn in_flight_downloads() -> &'static InFlightDownloads;
fn full_path(name: &str) -> PathBuf {
Self::root_path().join(name)
}
fn free_with_name(name: impl ToString) {
Self::storage_mut().remove(&name.to_string());
}
fn free(self: Weak<Self>) {
if self.is_null() {
return;
}
let mut storage = Self::storage_mut();
let key = storage
.iter()
.find(|(_, val)| val.addr() == self.addr())
.expect("Failed to find managed object to free.")
.0
.clone();
storage.remove(&key);
}
fn store_with_name<E>(name: &str, create: impl FnOnce() -> Result<T, E>) -> Result<Weak<T>, E> {
if let Some(entry) = Self::storage().get(name) {
return Ok(entry.weak());
}
let entry = Own::new(create()?);
Ok(insert_or_existing(Self::storage_mut(), name.to_owned(), entry))
}
unsafe fn get_static(self: Weak<Self>) -> &'static T {
let storage = Self::storage();
let rf = storage
.iter()
.find(|(_, val)| val.addr() == self.addr())
.expect("Failed to get_static managed")
.1;
unsafe { transmute(rf.deref()) }
}
fn get_existing(name: impl ToString) -> Option<Weak<T>> {
Self::storage().get(&name.to_string()).map(Own::weak)
}
fn get(name: impl ToString) -> Weak<T> {
let name = name.to_string();
if let Some(existing) = Self::storage().get(&name) {
return existing.weak();
}
let new = Own::new(T::load_path(&Self::full_path(&name)));
insert_or_existing(Self::storage_mut(), name, new)
}
fn load(data: &[u8], name: impl ToString) -> Weak<T> {
let name = name.to_string();
if let Some(existing) = Self::storage().get(&name) {
return existing.weak();
}
let new = Own::new(T::load_data(data, &name));
insert_or_existing(Self::storage_mut(), name, new)
}
#[allow(async_fn_in_trait)]
async fn download(name: impl ToString, url: &str) -> Result<Weak<T>> {
let name = name.to_string();
if let Some(existing) = Self::get_existing(&name) {
return Ok(existing);
}
let waiter = {
let mut in_flight = Self::in_flight_downloads().lock();
if let Some(existing) = Self::storage().get(&name) {
return Ok(existing.weak());
}
if let Some(event) = in_flight.get(&name) {
Some(event.listen())
} else {
in_flight.insert(name.clone(), Arc::new(Event::new()));
None
}
};
if let Some(listener) = waiter {
listener.await;
return Self::get_existing(&name)
.ok_or_else(|| anyhow!("Download of '{name}' failed in the task which started it"));
}
struct FinishGuard {
in_flight: &'static InFlightDownloads,
name: String,
}
impl Drop for FinishGuard {
fn drop(&mut self) {
if let Some(event) = self.in_flight.lock().remove(&self.name) {
event.notify(usize::MAX);
}
}
}
let _guard = FinishGuard {
in_flight: Self::in_flight_downloads(),
name: name.clone(),
};
let data = reqwest::get(url).await?.bytes().await?;
Ok(Self::load(&data, &name))
}
}