use std::sync::Arc;
use qubit_budget::InsufficientBudgetError;
use qubit_budget::ResourceBudget;
use crate::copy::CopyAssessment;
use crate::copy::CopyExecutionRoute;
use crate::copy::CopyOptions;
use crate::copy::FallbackRejection;
use crate::error::FsError;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
use crate::error::FsResult;
use crate::facade::internal::ByteBudget;
use crate::facade::internal::FileSystemResource;
use crate::metadata::FileSystemCapability;
use crate::metadata::FileSystemProperties;
use crate::path::Path;
use crate::spi::ProviderOperation;
use crate::spi::ProviderOperations;
use crate::spi::ProviderProperties;
use crate::write::WriteOptions;
pub(crate) struct FacadeCore {
properties: Arc<FileSystemProperties>,
provider_operations: ProviderOperations,
}
impl FacadeCore {
pub(crate) const PREFIX_BUFFER_SIZE: usize = 8192;
pub(crate) fn new(provider: ProviderProperties) -> FsResult<Self> {
let provider_operations = provider.operations();
let properties = FileSystemProperties::from_provider(&provider)?;
Ok(Self {
properties: Arc::new(properties),
provider_operations,
})
}
#[inline]
pub(crate) fn properties(&self) -> &FileSystemProperties {
&self.properties
}
#[inline]
pub(crate) fn provider_supports(&self, operation: ProviderOperation) -> bool {
self.provider_operations.supports(operation)
}
pub(crate) fn validate_path(&self, path: &Path, operation: FsOperation) -> FsResult<()> {
self.properties.validate_path(path, operation)
}
pub(crate) fn validate_temp_parent(&self, parent: Option<&Path>) -> FsResult<()> {
parent.map_or(Ok(()), |path| self.validate_path(path, FsOperation::CreateTemp))
}
pub(crate) fn validate_write_request(&self, path: &Path, options: &WriteOptions) -> FsResult<()> {
self.validate_path(path, FsOperation::OpenWriter)?;
options
.validate_against(self.properties.capabilities())
.map_err(|error| self.enrich(error, Some(path), FsOperation::OpenWriter))?;
self.require(FileSystemCapability::Write, FsOperation::OpenWriter, Some(path))
}
pub(crate) fn assess_copy(&self, source: &Path, target: &Path, options: &CopyOptions) -> FsResult<CopyAssessment> {
self.validate_path(source, FsOperation::Copy)?;
self.validate_path(target, FsOperation::Copy)?;
if source == target {
return Err(FsError::new(
FsErrorKind::InvalidOptions,
FsOperation::Copy,
"copy source and target must differ",
)
.with_path(source.clone())
.with_target(target.clone()));
}
options
.validate_against(self.properties.capabilities())
.map_err(|error| {
self.enrich(error, Some(source), FsOperation::Copy)
.with_target(target.clone())
})?;
if options.max_entries() == Some(0) {
return Err(FsError::new(
FsErrorKind::RequirementNotMet,
FsOperation::Copy,
"copy entry limit must be greater than zero",
));
}
let mut rejection = crate::copy::fallback_rejection(options, self.properties.symlink_policy());
if rejection.is_none() {
if !self.properties.capabilities().supports(FileSystemCapability::Read)
|| !self.provider_operations.supports(ProviderOperation::OpenReader)
{
rejection = Some(FallbackRejection::MissingRead);
} else if !self.properties.capabilities().supports(FileSystemCapability::Write)
|| !self.provider_operations.supports(ProviderOperation::OpenWriter)
{
rejection = Some(FallbackRejection::MissingWrite);
} else if !self.provider_operations.supports(ProviderOperation::Stat) {
rejection = Some(FallbackRejection::MissingStat);
}
}
let provider_copy = self.provider_operations.supports(ProviderOperation::TryCopy);
match (provider_copy, rejection) {
(true, reason @ None) => Ok(CopyAssessment::new(CopyExecutionRoute::ProviderThenStream, reason)),
(true, reason @ Some(_)) => Ok(CopyAssessment::new(CopyExecutionRoute::ProviderOnly, reason)),
(false, None) => Ok(CopyAssessment::new(CopyExecutionRoute::StreamOnly, None)),
(false, Some(_)) => Err(FsError::new(
FsErrorKind::RequirementNotMet,
FsOperation::Copy,
"copy has no available provider or stream execution path",
)),
}
}
pub(crate) fn require(
&self,
capability: FileSystemCapability,
operation: FsOperation,
path: Option<&Path>,
) -> FsResult<()> {
if self.properties.capabilities().supports(capability) {
Ok(())
} else {
let error = FsError::new(
FsErrorKind::UnsupportedCapability,
operation,
"filesystem capability is not supported",
)
.with_required_capability(capability)
.with_missing_provider(self.properties.info().provider_id());
Err(match path {
Some(path) => error.with_path(path.clone()),
None => error,
})
}
}
pub(crate) fn enrich(&self, error: FsError, path: Option<&Path>, operation: FsOperation) -> FsError {
let error = error
.with_operation(operation)
.with_missing_provider(self.properties.info().provider_id());
match path {
Some(path) => error.with_missing_context(path, None, self.properties.info().provider_id()),
None => error,
}
}
pub(crate) fn contract_error(&self, path: &Path, operation: FsOperation, message: &str) -> FsError {
FsError::new(FsErrorKind::ProviderContractViolation, operation, message)
.with_path(path.clone())
.with_provider(self.properties.info().provider_id())
}
#[inline]
pub(crate) fn byte_budget(resource: FileSystemResource, maximum: u64) -> ByteBudget {
ResourceBudget::new(resource, maximum)
}
#[inline]
pub(crate) fn quantity_from_usize(
value: usize,
operation: FsOperation,
path: &Path,
provider: &str,
) -> Result<u64, FsError> {
u64::try_from(value).map_err(|error| {
FsError::with_source(
FsErrorKind::ResourceLimitExceeded,
operation,
"I/O byte count cannot be represented by the resource budget",
error,
)
.with_path(path.clone())
.with_provider(provider)
})
}
#[inline]
pub(crate) fn budget_error(
error: InsufficientBudgetError<FileSystemResource, u64>,
operation: FsOperation,
path: &Path,
provider: &str,
message: &'static str,
) -> FsError {
FsError::with_source(FsErrorKind::ResourceLimitExceeded, operation, message, error)
.with_path(path.clone())
.with_provider(provider)
}
#[inline]
pub(crate) fn next_prefix_read_len(accumulated: usize, maximum: usize) -> usize {
maximum.saturating_sub(accumulated).min(Self::PREFIX_BUFFER_SIZE)
}
}
#[cfg(test)]
mod tests {
use super::FacadeCore;
use crate::error::FsOperation;
use crate::path::Path;
#[test]
fn quantity_conversion_is_executed_at_runtime() {
let path = Path::parse("/facade-core").expect("valid path");
assert_eq!(
FacadeCore::quantity_from_usize(7, FsOperation::Read, &path, "test").expect("value fits"),
7,
);
}
}