mod reference_counting_hash_map;
mod resource;
#[cfg(test)]
mod tests;
use crate::{
schema::array_uri_reference::{AbsoluteUri, ArrayUriReference, Scheme, Uri},
strided_array_file::{extract_array_index, ArrayProxy, Error, StridedArrayFile},
};
use reference_counting_hash_map::ReferenceCountingHashMap;
use resource::FileResource;
use serde::Deserialize;
use std::{
collections::HashMap,
fs::{create_dir, File},
io::BufWriter,
mem::transmute,
path::{Path, PathBuf},
sync::{Arc, Mutex, OnceLock, Weak},
};
use uuid::Uuid;
use zip::ZipArchive;
type FileArchive = ZipArchive<File>;
type SharedFileArchive = Arc<Mutex<FileArchive>>;
pub fn configure(temporary_directory: PathBuf) -> bool {
if !temporary_directory.is_dir() {
return false;
}
INSTANCE
.set(Arc::new(Mutex::new(ArrayCache {
resource_views: ReferenceCountingHashMap::new(),
file_resources: Vec::new(),
resources: Vec::new(),
file_archives: HashMap::new(),
temporary_directory,
})))
.is_ok()
}
static INSTANCE: OnceLock<Arc<Mutex<ArrayCache>>> = OnceLock::new();
#[derive(Debug)]
pub(crate) struct ArrayCache {
resource_views: ReferenceCountingHashMap<Uri, Arc<dyn resource::View<'static>>>,
file_resources: Vec<Weak<StridedArrayFile<'static>>>,
resources: Vec<Weak<dyn resource::Resource + 'static>>,
file_archives: HashMap<AbsoluteUri, SharedFileArchive>,
temporary_directory: PathBuf,
}
pub(crate) trait UriResolvable: for<'de> Deserialize<'de> {
fn set_base_for_relative_uris(&mut self, base_uri: &AbsoluteUri);
}
impl ArrayCache {
pub(crate) fn register(uri: &ArrayUriReference) {
let resolved = match uri.resolved() {
Ok(resolved) => resolved,
Err(_) => return,
};
let instance = Self::instance();
let mut cache = instance.lock().unwrap();
if cache.resource_views.increment(&resolved).is_some() {
}
else if let Some(file_view) = cache.create_file_view(uri) {
cache.resource_views.insert(resolved, file_view).unwrap();
}
}
pub(crate) fn deregister(uri: &ArrayUriReference) {
let absolute = match uri.resolved() {
Ok(value) => value,
_ => return,
};
let instance = Self::instance();
let mut cache = instance.lock().unwrap();
if cache.resource_views.decrement(&absolute) != Some(0) {
return;
}
cache.resources.retain(|r| r.strong_count() > 0);
cache.file_resources.retain(|r| r.strong_count() > 0);
cache.consider_removing_archive(uri);
}
pub(crate) fn get(uri: &ArrayUriReference) -> Result<ArrayProxy<'_>, Error> {
let resolved = uri.resolved()?;
let instance = Self::instance();
let mut cache = instance.lock().unwrap();
cache.consider_fetching_file(uri)?;
let proxy = cache.resource_views.get(&resolved)?.proxy()?;
unsafe { Ok(transmute::<ArrayProxy<'static>, ArrayProxy<'_>>(proxy)) }
}
pub(crate) fn insert(proxy: ArrayProxy<'_>) -> Result<ArrayUriReference, Error> {
let uuid = Uuid::new_v4();
let uri: ArrayUriReference = format!("memory:{uuid}").as_str().try_into()?;
let resolved = uri.resolved()?;
let instance = Self::instance();
let mut cache = instance.lock().unwrap();
for resource in &cache.resources {
let resource = resource.upgrade().unwrap();
if resource.is_parent_of(proxy.clone())? {
let proxy = unsafe { transmute::<ArrayProxy<'_>, ArrayProxy<'static>>(proxy) };
let resource_view = resource::create_view(proxy, resource)?;
cache.resource_views.insert(resolved, resource_view)?;
return Ok(uri);
}
}
let array_copy = proxy.to_owned()?;
let resource_view = resource::create_new_array(array_copy)?;
let resource = Arc::downgrade(&resource_view.resource());
cache.resources.push(resource);
cache.resource_views.insert(resolved, resource_view)?;
Ok(uri)
}
pub(crate) fn insert_archive(archive: FileArchive) -> Result<AbsoluteUri, Error> {
let archive = Arc::new(Mutex::new(archive));
let instance = Self::instance();
let mut cache = instance.lock().unwrap();
let path = cache.temporary_directory().join(Uuid::new_v4().to_string());
let uri = AbsoluteUri::try_from(path.as_path())?;
cache.file_archives.insert(uri.clone(), archive);
Ok(uri)
}
fn instance() -> Arc<Mutex<Self>> {
INSTANCE
.get_or_init(|| {
Arc::new(Mutex::new(ArrayCache {
resource_views: ReferenceCountingHashMap::new(),
file_resources: Vec::new(),
resources: Vec::new(),
file_archives: HashMap::new(),
temporary_directory: std::env::temp_dir(),
}))
})
.clone()
}
fn temporary_directory(&self) -> &Path {
&self.temporary_directory
}
fn consider_fetching_file(&mut self, uri: &ArrayUriReference) -> Result<(), Error> {
let destination_path: PathBuf = if uri.scheme() == Some(Scheme::FILE) {
let (absolute, _fragment) = uri.resolved()?.to_absolute_and_fragment();
(&absolute).try_into()?
} else {
return Ok(());
};
if destination_path.is_file() {
return Ok(());
}
let base = match uri.base() {
Some(value) => value,
None => return Ok(()),
};
let mut archive = match self.file_archives.get(base) {
Some(value) => value.lock().unwrap(),
None => return Ok(()),
};
let source_path = {
let mut relative = uri.relative().to_owned();
relative.set_fragment(None);
relative
};
let mut source_file = archive
.by_name(source_path.as_str())
.map_err(|e| e.to_string())?;
let destination_directory = destination_path.parent().unwrap();
if !destination_directory.is_dir() {
create_dir(destination_directory)
.map_err(|e| format!("Failed to create archive subdirectory {e}"))?;
}
let destination_file = File::create(destination_path).map_err(|e| e.to_string())?;
let mut writer = BufWriter::new(destination_file);
let _bytes_copied =
std::io::copy(&mut source_file, &mut writer).map_err(|e| e.to_string())?;
Ok(())
}
fn consider_removing_archive(&mut self, uri: &ArrayUriReference) {
let base = match uri.base() {
Some(value) => value,
None => return,
};
if !self.file_archives.contains_key(base) {
return;
}
let extraction_directory: PathBuf = match base.try_into() {
Ok(path) => path,
Err(_) => return,
};
for resource in &self.file_resources {
let file = resource.upgrade().unwrap();
if file.path().starts_with(&extraction_directory) {
return;
}
}
self.file_archives.remove(base).unwrap();
remove_directory_gently(&extraction_directory);
}
fn create_file_view(
&mut self,
uri: &ArrayUriReference,
) -> Option<Arc<dyn resource::View<'static>>> {
let path = extract_strided_array_file_path(uri)?;
let array_index = match extract_array_index(uri) {
Ok(value) => value.unwrap_or(0),
Err(_) => return None,
};
for weak_ptr in &self.file_resources {
let file = weak_ptr.upgrade().unwrap();
if file.path() == path {
return Some(file.create_view(array_index));
}
}
let file = Arc::new(StridedArrayFile::new(path));
let weak_ptr = Arc::downgrade(&file);
self.file_resources.push(weak_ptr.clone());
self.resources.push(weak_ptr);
Some(file.create_view(array_index))
}
#[cfg(test)]
fn reference_count(uri: &ArrayUriReference) -> Result<usize, Error> {
let instance = Self::instance();
let cache = instance.lock().unwrap();
Ok(cache.resource_views.reference_count(&uri.resolved()?))
}
}
fn extract_strided_array_file_path(uri: &ArrayUriReference) -> Option<PathBuf> {
let resolved = match uri.resolved() {
Ok(value) => value,
Err(_) => return None,
};
let (absolute, _fragment) = resolved.to_absolute_and_fragment();
(&absolute).try_into().ok()
}
fn remove_directory_gently(path: &Path) {
let is_directory = std::fs::metadata(path).is_ok_and(|m| m.is_dir());
if is_directory {
let _result = std::fs::remove_dir_all(path);
}
}