use std::{
io,
path::{Path, PathBuf},
sync::OnceLock,
};
static ROOT: OnceLock<PathBuf> = OnceLock::new();
pub fn configure_app(qualifier: &str, organization: &str, application: &str) -> io::Result<()> {
if application.trim().is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"application name must not be empty",
));
}
for component in [qualifier, organization, application] {
if component.contains(['/', '\\', '\0']) || matches!(component, "." | "..") {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"application identity must not contain path components",
));
}
}
configure_root(platform_root(qualifier, organization, application)?)
}
pub fn configure_root(path: impl AsRef<Path>) -> io::Result<()> {
let path = path.as_ref();
if path.as_os_str().is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"storage root must not be empty",
));
}
let path = std::path::absolute(path)?;
ROOT.set(path).map_err(|_| {
io::Error::new(
io::ErrorKind::AlreadyExists,
"native storage root is already configured",
)
})
}
pub(super) fn configured_root() -> io::Result<&'static Path> {
ROOT.get().map(PathBuf::as_path).ok_or_else(|| {
io::Error::new(io::ErrorKind::NotFound, "native storage is not configured; call opfs::persistent::configure_app or configure_root at startup")
})
}
#[cfg(not(target_os = "ios"))]
fn platform_root(qualifier: &str, organization: &str, application: &str) -> io::Result<PathBuf> {
directories::ProjectDirs::from(qualifier, organization, application)
.map(|dirs| dirs.data_local_dir().to_owned())
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"could not find application data directory",
)
})
}
#[cfg(target_os = "ios")]
fn platform_root(_: &str, _: &str, _: &str) -> io::Result<PathBuf> {
use objc2_foundation::{
NSSearchPathDirectory, NSSearchPathDomainMask, NSSearchPathForDirectoriesInDomains,
};
let paths = NSSearchPathForDirectoriesInDomains(
NSSearchPathDirectory::ApplicationSupportDirectory,
NSSearchPathDomainMask::UserDomainMask,
true,
);
paths
.firstObject()
.map(|path| PathBuf::from(path.to_string()))
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"could not find application-container support directory",
)
})
}
#[cfg(all(test, not(target_os = "ios")))]
mod tests {
use super::*;
#[test]
fn identities_resolve_to_separate_local_app_directories() {
let root = platform_root("org", "OPFS", "Example").unwrap();
let other = platform_root("org", "OPFS", "Other").unwrap();
assert_ne!(root, other);
assert_eq!(
root,
directories::ProjectDirs::from("org", "OPFS", "Example")
.unwrap()
.data_local_dir()
);
}
#[cfg(target_os = "macos")]
#[test]
fn yap_keeps_its_existing_application_support_path() {
let base = directories::BaseDirs::new().unwrap();
assert_eq!(
platform_root("", "", "Yap").unwrap(),
base.data_dir().join("Yap")
);
}
}