use std::sync::Arc;
use async_trait::async_trait;
use crate::client::MasterClient;
use crate::config::{GoosefsConfig, WriteType};
use crate::context::FileSystemContext;
use crate::error::{Error, Result};
use crate::fs::filesystem::FileSystem;
use crate::fs::options::{
CreateFileOptions, DeleteOptions, GetStatusOptions, ListStatusOptions, OpenFileOptions,
};
use crate::fs::uri_status::URIStatus;
use crate::fs::write_type::{get_write_type_from_xattr, WriteTypeXAttr};
use crate::io::{GoosefsFileInStream, GoosefsFileWriter};
use crate::proto::grpc::file::{CreateFilePOptions, WritePType};
pub struct BaseFileSystem {
ctx: Arc<FileSystemContext>,
config: GoosefsConfig,
}
impl BaseFileSystem {
pub fn from_context(ctx: Arc<FileSystemContext>) -> Arc<Self> {
let config = ctx.config().clone();
Arc::new(Self { config, ctx })
}
pub async fn connect(config: GoosefsConfig) -> Result<Arc<Self>> {
let ctx = FileSystemContext::connect(config).await?;
Ok(Self::from_context(ctx))
}
pub fn config(&self) -> &GoosefsConfig {
&self.config
}
pub fn context(&self) -> &Arc<FileSystemContext> {
&self.ctx
}
fn master(&self) -> Arc<MasterClient> {
self.ctx.acquire_master()
}
async fn resolve_write_type(&self, path: &str, options: &CreateFileOptions) -> WriteType {
if let WriteTypeXAttr::Explicit(wt) = options.write_type {
return wt;
}
let parent = Self::parent_path(path);
if let Some(parent_path) = parent {
let master = self.master();
match master.get_status(&parent_path).await {
Ok(parent_info) => {
let parent_status = URIStatus::from_proto(parent_info);
if let Some(wt) = get_write_type_from_xattr(&parent_status.xattr) {
return wt;
}
}
Err(e) if e.is_not_found() => {
}
Err(e) => {
tracing::warn!(
path = %path,
parent = %parent_path,
error = %e,
"resolve_write_type: failed to fetch parent xattr; \
falling back to config default — file will be created with that WriteType"
);
}
}
}
if let Some(proto_wt) = self.config.get_write_type() {
if let Ok(wt) = WriteType::try_from_proto(proto_wt) {
return wt;
}
}
WriteType::MustCache
}
fn parent_path(path: &str) -> Option<String> {
crate::metadata_cache::parent_path(path)
}
pub async fn write_file(
&self,
path: &str,
data: &[u8],
options: CreateFileOptions,
) -> Result<u64> {
let write_type = self.resolve_write_type(path, &options).await;
let proto_opts = CreateFilePOptions {
block_size_bytes: options.block_size_bytes,
recursive: Some(options.recursive),
write_type: Some(WritePType::from(write_type) as i32),
..Default::default()
};
GoosefsFileWriter::write_file_with_context_and_options(
self.ctx.clone(),
path,
data,
Some(proto_opts),
)
.await
}
}
#[async_trait]
impl FileSystem for BaseFileSystem {
async fn get_status(&self, path: &str) -> Result<URIStatus> {
self.get_status_with_options(path, GetStatusOptions::default())
.await
}
async fn get_status_with_options(
&self,
path: &str,
opts: GetStatusOptions,
) -> Result<URIStatus> {
let sync = opts
.sync_interval_ms
.unwrap_or(self.config.file_metadata_sync_interval);
let load = opts
.load_metadata_type
.unwrap_or(self.config.file_metadata_load_type);
let master = self.master();
let cache = self.ctx.acquire_metadata_cache();
let mut fi =
crate::metadata_cache::get_status_through_cache(cache.as_deref(), path, sync, || {
master.get_status_with_load_type(path, Some(load), Some(sync))
})
.await?;
let check = self.ctx.config().check_block_replicas;
if check > 0 {
let router = self.ctx.acquire_router();
let view = crate::block::router::WorkerRouterView::from_shared(&router);
let pool = self.ctx.acquire_worker_pool();
crate::block::maybe_enrich_file_block_locations(
&mut fi,
&view,
Some(&pool),
self.ctx.config(),
check,
)
.await;
} else {
crate::block::ensure_block_ids_from_file_block_infos(&mut fi);
crate::block::fill_in_goosefs_percentage_without_probe(&mut fi);
}
Ok(URIStatus::from_proto(fi))
}
async fn list_status(&self, path: &str, recursive: bool) -> Result<Vec<URIStatus>> {
self.list_status_with_options(
path,
ListStatusOptions {
recursive,
..Default::default()
},
)
.await
}
async fn list_status_with_options(
&self,
path: &str,
opts: ListStatusOptions,
) -> Result<Vec<URIStatus>> {
let master = self.master();
let sync = opts
.sync_interval_ms
.unwrap_or(self.config.file_metadata_sync_interval);
let load = opts
.load_metadata_type
.unwrap_or(self.config.file_metadata_load_type);
if opts.recursive {
let items = master
.list_status_with_options(path, true, Some(load), Some(sync))
.await?;
return Ok(items.into_iter().map(URIStatus::from_proto).collect());
}
let skip = crate::metadata_cache::should_skip_listing_cache(
false,
load,
opts.load_metadata_only,
sync,
);
let cache = self.ctx.acquire_metadata_cache();
let items =
crate::metadata_cache::list_status_through_cache(cache.as_deref(), path, skip, || {
master.list_status_with_options(path, false, Some(load), Some(sync))
})
.await?;
Ok(items.into_iter().map(URIStatus::from_proto).collect())
}
async fn exists(&self, path: &str) -> Result<bool> {
match self.get_status(path).await {
Ok(status) => {
Ok(status.is_readable())
}
Err(Error::NotFound { .. }) => Ok(false),
Err(e) => Err(e),
}
}
async fn open_file(&self, path: &str, options: OpenFileOptions) -> Result<GoosefsFileInStream> {
GoosefsFileInStream::open_with_context(self.ctx.clone(), path, options).await
}
async fn create_file(
&self,
path: &str,
options: CreateFileOptions,
) -> Result<GoosefsFileWriter> {
let write_type = self.resolve_write_type(path, &options).await;
let proto_opts = CreateFilePOptions {
block_size_bytes: options.block_size_bytes,
recursive: Some(options.recursive),
write_type: Some(WritePType::from(write_type) as i32),
..Default::default()
};
GoosefsFileWriter::create_with_context(self.ctx.clone(), path, Some(proto_opts)).await
}
async fn mkdir(&self, path: &str, recursive: bool) -> Result<()> {
let master = self.master();
crate::metadata_cache::invalidate_on_success(
self.ctx.acquire_metadata_cache().as_deref(),
path,
master.create_directory(path, recursive).await,
)
}
async fn delete(&self, path: &str, options: DeleteOptions) -> Result<()> {
let master = self.master();
crate::metadata_cache::invalidate_on_success(
self.ctx.acquire_metadata_cache().as_deref(),
path,
master.delete_with_options(path, options).await,
)
}
async fn rename(&self, src: &str, dst: &str) -> Result<()> {
let master = self.master();
crate::metadata_cache::invalidate_rename_on_success(
self.ctx.acquire_metadata_cache().as_deref(),
src,
dst,
master.rename(src, dst).await,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parent_path_normal() {
assert_eq!(
BaseFileSystem::parent_path("/data/hello.txt"),
Some("/data".to_string())
);
}
#[test]
fn test_parent_path_root_child() {
assert_eq!(
BaseFileSystem::parent_path("/hello.txt"),
Some("/".to_string())
);
}
#[test]
fn test_parent_path_root() {
assert_eq!(BaseFileSystem::parent_path("/"), None);
}
#[test]
fn test_parent_path_nested() {
assert_eq!(
BaseFileSystem::parent_path("/a/b/c/file.parquet"),
Some("/a/b/c".to_string())
);
}
#[test]
fn test_parent_path_trailing_slash() {
assert_eq!(
BaseFileSystem::parent_path("/data/dir/"),
Some("/data".to_string())
);
}
#[test]
fn test_from_context_sets_ctx() {
}
}