mod backend;
mod change;
mod content;
mod db;
mod directory;
mod mutate;
mod resolve;
mod search;
mod trash;
mod workspace;
use std::path::Path;
use fslite_core::{
BatchOperation, BatchResult, Capability, Change, ChangeCursor, ContentQuery, CopyOptions,
CreateOptions, FileRead, FindQuery, FsError, FsResult, LinkTarget, MoveOptions,
MutationOptions, Node, Page, PageRequest, ReadOptions, RemoveOptions, RequestContext,
SearchMatch, StatOptions, TouchOptions, TrashEntry, TrashId, TreeEntry, TreeOptions,
VirtualPath, WorkspaceId, WorkspaceUsage, WriteOptions, WriteSource,
};
pub use db::ConnectOptions;
pub use fslite_core::FileSystem;
pub use workspace::{Workspace, WorkspaceOptions};
pub struct SqliteFileSystem {
conn: tokio_rusqlite::Connection,
}
impl SqliteFileSystem {
pub async fn open(path: impl AsRef<Path>, options: ConnectOptions) -> FsResult<Self> {
let conn = db::open_file(path.as_ref(), options).await?;
Ok(Self { conn })
}
pub async fn open_in_memory(options: ConnectOptions) -> FsResult<Self> {
let conn = db::open_memory(options).await?;
Ok(Self { conn })
}
pub async fn create_workspace(&self, options: WorkspaceOptions) -> FsResult<Workspace> {
workspace::create_workspace(&self.conn, options).await
}
pub async fn delete_workspace(&self, workspace_id: WorkspaceId) -> FsResult<()> {
workspace::delete_workspace(&self.conn, workspace_id).await
}
pub async fn workspace_usage(&self, ctx: &RequestContext) -> FsResult<WorkspaceUsage> {
require_capability(ctx, Capability::Read, ctx.workspace_id)?;
workspace::workspace_usage(&self.conn, ctx.workspace_id).await
}
pub async fn stat(
&self,
ctx: &RequestContext,
path: &VirtualPath,
options: StatOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Read, path)?;
directory::stat(
&self.conn,
ctx.workspace_id,
path.clone(),
options.follow_symlinks,
)
.await
}
pub async fn exists(
&self,
ctx: &RequestContext,
path: &VirtualPath,
options: StatOptions,
) -> FsResult<bool> {
require_capability(ctx, Capability::Read, path)?;
directory::exists(
&self.conn,
ctx.workspace_id,
path.clone(),
options.follow_symlinks,
)
.await
}
pub async fn read_dir(
&self,
ctx: &RequestContext,
path: &VirtualPath,
page: PageRequest,
) -> FsResult<Page<Node>> {
require_capability(ctx, Capability::Read, path)?;
directory::read_dir(&self.conn, ctx.workspace_id, path.clone(), page).await
}
pub async fn tree(
&self,
ctx: &RequestContext,
path: &VirtualPath,
options: TreeOptions,
page: PageRequest,
) -> FsResult<Page<TreeEntry>> {
require_capability(ctx, Capability::Read, path)?;
directory::tree(&self.conn, ctx.workspace_id, path.clone(), options, page).await
}
pub async fn mkdir(
&self,
ctx: &RequestContext,
path: &VirtualPath,
options: CreateOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, path)?;
directory::mkdir(
&self.conn,
ctx.workspace_id,
path.clone(),
options,
actor_json(ctx),
)
.await
}
pub async fn read(
&self,
ctx: &RequestContext,
path: &VirtualPath,
options: ReadOptions,
) -> FsResult<FileRead> {
require_capability(ctx, Capability::Read, path)?;
content::read(&self.conn, ctx.workspace_id, path.clone(), options).await
}
pub async fn write(
&self,
ctx: &RequestContext,
path: &VirtualPath,
source: WriteSource,
options: WriteOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, path)?;
content::write(
&self.conn,
ctx.workspace_id,
path.clone(),
source,
options,
actor_json(ctx),
)
.await
}
pub async fn write_at(
&self,
ctx: &RequestContext,
path: &VirtualPath,
offset: u64,
source: WriteSource,
options: WriteOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, path)?;
content::write_at(
&self.conn,
ctx.workspace_id,
path.clone(),
offset,
source,
options,
actor_json(ctx),
)
.await
}
pub async fn append(
&self,
ctx: &RequestContext,
path: &VirtualPath,
source: WriteSource,
options: WriteOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, path)?;
content::append(
&self.conn,
ctx.workspace_id,
path.clone(),
source,
options,
actor_json(ctx),
)
.await
}
pub async fn truncate(
&self,
ctx: &RequestContext,
path: &VirtualPath,
length: u64,
options: MutationOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, path)?;
content::truncate(
&self.conn,
ctx.workspace_id,
path.clone(),
length,
options,
actor_json(ctx),
)
.await
}
pub async fn touch(
&self,
ctx: &RequestContext,
path: &VirtualPath,
options: TouchOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, path)?;
content::touch(
&self.conn,
ctx.workspace_id,
path.clone(),
options,
actor_json(ctx),
)
.await
}
pub async fn copy(
&self,
ctx: &RequestContext,
from: &VirtualPath,
to: &VirtualPath,
options: CopyOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, to)?;
mutate::copy(
&self.conn,
ctx.workspace_id,
from.clone(),
to.clone(),
options,
actor_json(ctx),
)
.await
}
pub async fn move_path(
&self,
ctx: &RequestContext,
from: &VirtualPath,
to: &VirtualPath,
options: MoveOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, to)?;
mutate::move_path(
&self.conn,
ctx.workspace_id,
from.clone(),
to.clone(),
options,
actor_json(ctx),
)
.await
}
pub async fn remove(
&self,
ctx: &RequestContext,
path: &VirtualPath,
options: RemoveOptions,
) -> FsResult<()> {
require_capability(ctx, Capability::Delete, path)?;
mutate::remove(
&self.conn,
ctx.workspace_id,
path.clone(),
options,
actor_json(ctx),
)
.await
}
pub async fn symlink(
&self,
ctx: &RequestContext,
target: &LinkTarget,
link: &VirtualPath,
options: CreateOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, link)?;
mutate::symlink(
&self.conn,
ctx.workspace_id,
target.clone(),
link.clone(),
options,
actor_json(ctx),
)
.await
}
pub async fn read_link(
&self,
ctx: &RequestContext,
path: &VirtualPath,
) -> FsResult<LinkTarget> {
require_capability(ctx, Capability::Read, path)?;
mutate::read_link(&self.conn, ctx.workspace_id, path.clone()).await
}
pub async fn trash(
&self,
ctx: &RequestContext,
path: &VirtualPath,
options: MutationOptions,
) -> FsResult<TrashEntry> {
require_capability(ctx, Capability::TrashRestore, path)?;
trash::trash(
&self.conn,
ctx.workspace_id,
path.clone(),
options,
actor_json(ctx),
)
.await
}
pub async fn list_trash(
&self,
ctx: &RequestContext,
page: PageRequest,
) -> FsResult<Page<TrashEntry>> {
require_capability(ctx, Capability::Read, ctx.workspace_id)?;
trash::list_trash(&self.conn, ctx.workspace_id, page).await
}
pub async fn restore(
&self,
ctx: &RequestContext,
trash_id: TrashId,
destination: Option<&VirtualPath>,
options: MutationOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::TrashRestore, ctx.workspace_id)?;
trash::restore(
&self.conn,
ctx.workspace_id,
trash_id,
destination.cloned(),
options,
actor_json(ctx),
)
.await
}
pub async fn purge(&self, ctx: &RequestContext, trash_id: TrashId) -> FsResult<()> {
require_capability(ctx, Capability::Delete, ctx.workspace_id)?;
trash::purge(&self.conn, ctx.workspace_id, trash_id, actor_json(ctx)).await
}
pub async fn set_attribute(
&self,
ctx: &RequestContext,
path: &VirtualPath,
key: &str,
value: &[u8],
options: MutationOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, path)?;
mutate::set_attribute(
&self.conn,
ctx.workspace_id,
path.clone(),
key.to_string(),
value.to_vec(),
options,
actor_json(ctx),
)
.await
}
pub async fn remove_attribute(
&self,
ctx: &RequestContext,
path: &VirtualPath,
key: &str,
options: MutationOptions,
) -> FsResult<Node> {
require_capability(ctx, Capability::Write, path)?;
mutate::remove_attribute(
&self.conn,
ctx.workspace_id,
path.clone(),
key.to_string(),
options,
actor_json(ctx),
)
.await
}
pub async fn batch(
&self,
ctx: &RequestContext,
operations: Vec<BatchOperation>,
) -> FsResult<Vec<BatchResult>> {
mutate::batch(&self.conn, ctx, operations, actor_json(ctx)).await
}
pub async fn glob(
&self,
ctx: &RequestContext,
pattern: &str,
page: PageRequest,
) -> FsResult<Page<Node>> {
require_capability(ctx, Capability::Read, ctx.workspace_id)?;
search::glob(&self.conn, ctx.workspace_id, pattern.to_string(), page).await
}
pub async fn find(
&self,
ctx: &RequestContext,
query: FindQuery,
page: PageRequest,
) -> FsResult<Page<Node>> {
require_capability(ctx, Capability::Read, ctx.workspace_id)?;
search::find(&self.conn, ctx.workspace_id, query, page).await
}
pub async fn search_content(
&self,
ctx: &RequestContext,
query: ContentQuery,
page: PageRequest,
) -> FsResult<Page<SearchMatch>> {
require_capability(ctx, Capability::Read, ctx.workspace_id)?;
search::search_content(&self.conn, ctx.workspace_id, query, page).await
}
pub async fn changes(
&self,
ctx: &RequestContext,
after: Option<ChangeCursor>,
page: PageRequest,
) -> FsResult<Page<Change>> {
require_capability(ctx, Capability::Read, ctx.workspace_id)?;
change::changes(&self.conn, ctx.workspace_id, after, page).await
}
}
fn actor_json(ctx: &RequestContext) -> String {
serde_json::to_string(&ctx.actor_metadata).unwrap_or_else(|_| "{}".to_string())
}
fn require_capability(
ctx: &RequestContext,
capability: Capability,
subject: impl std::fmt::Display,
) -> FsResult<()> {
if ctx.has_capability(capability) {
Ok(())
} else {
Err(FsError::permission_denied(subject))
}
}