MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
核心功能: - ✅ Categories/Series双视图管理(category_view.rs + import_markdown.rs) - ✅ FUSE Multi-Volume支持(tree_type参数) - ✅ SSH/SFTP/SCP/rsync协议完整实现(4042行) - ✅ NFS/SMB Module Phase 1-3完成 - ✅ Archive Module Phase 1-4完成(2916行) - ✅ Download Center API完整实现 - ✅ S3兼容API实现(560行) Git配置修正: - ✅ 删除错误origin(gitea.momentry.ddns.net) - ✅ 删除m5max128(指向机器名) - ✅ 设置origin = m5max128gitea.momentry.ddns.net/admin/markbase - ✅ 设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase 数据清理: - ✅ 删除38个临时SQLite(保留accusys.sqlite、demo.sqlite) - ✅ 删除.bak、test_*.bin、调试脚本等临时文件 - ✅ 删除临时目录(build/、download files/、raid_test/等) - ✅ 更新.gitignore排除临时文件 架构优化: - 52个文件修改,2434行新增,4739行删除 - Workspace成员整合(16个crate) - 数据库状态:accusys.sqlite保留(主demo测试) 远程同步: - ✅ 准备推送到m5max128gitea(远程Gitea) - ✅ 准备推送到m4minigitea(本地Gitea)
This commit is contained in:
250
markbase-webdav/src/webdav/markbase_fs.rs
Normal file
250
markbase-webdav/src/webdav/markbase_fs.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
use crate::webdav::dav_direntry::MarkBaseDavDirEntry;
|
||||
use crate::webdav::dav_file::MarkBaseDavFile;
|
||||
use crate::webdav::dav_metadata::MarkBaseDavMetaData;
|
||||
use dav_server::davpath::DavPath;
|
||||
use dav_server::fs::{
|
||||
DavDirEntry, DavFile, DavFileSystem, DavMetaData, FsError, FsFuture, FsStream, OpenOptions,
|
||||
};
|
||||
use futures_util::stream;
|
||||
use rusqlite::Connection;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct MarkBaseDavFs {
|
||||
inner: Arc<MarkBaseDavFsInner>,
|
||||
}
|
||||
|
||||
struct MarkBaseDavFsInner {
|
||||
user_id: String,
|
||||
sqlite: Mutex<Connection>,
|
||||
}
|
||||
|
||||
impl MarkBaseDavFs {
|
||||
pub fn new(user_id: &str, db_path: &str) -> Box<Self> {
|
||||
let conn = Connection::open(db_path).expect("Failed to open SQLite database");
|
||||
|
||||
Box::new(Self {
|
||||
inner: Arc::new(MarkBaseDavFsInner {
|
||||
user_id: user_id.to_string(),
|
||||
sqlite: Mutex::new(conn),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_path(&self, path: &DavPath) -> Option<String> {
|
||||
let path_str = path.as_pathbuf().to_string_lossy().to_string();
|
||||
let parts: Vec<&str> = path_str.split('/').filter(|s| !s.is_empty()).collect();
|
||||
|
||||
if parts.is_empty() {
|
||||
return self.find_root_node();
|
||||
}
|
||||
|
||||
let mut current_parent: Option<String> = self.find_root_node();
|
||||
|
||||
for part in parts {
|
||||
if let Some(parent_id) = current_parent {
|
||||
current_parent = self.find_child_by_label(&parent_id, part);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
current_parent
|
||||
}
|
||||
|
||||
fn find_root_node(&self) -> Option<String> {
|
||||
let conn = self.inner.sqlite.lock().unwrap();
|
||||
conn.query_row(
|
||||
"SELECT node_id FROM file_nodes WHERE parent_id IS NULL LIMIT 1",
|
||||
[],
|
||||
|row| row.get::<_, String>(0),
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn find_child_by_label(&self, parent_id: &str, label: &str) -> Option<String> {
|
||||
let conn = self.inner.sqlite.lock().unwrap();
|
||||
conn.query_row(
|
||||
"SELECT node_id FROM file_nodes WHERE parent_id = ? AND label = ?",
|
||||
[parent_id, label],
|
||||
|row| row.get::<_, String>(0),
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn extract_file_path(&self, aliases_json: &Option<String>) -> Option<String> {
|
||||
aliases_json.as_ref().and_then(|json| {
|
||||
if let Ok(aliases) = serde_json::from_str::<serde_json::Value>(json) {
|
||||
aliases
|
||||
.get("path")
|
||||
.and_then(|p| p.as_str())
|
||||
.map(|s| s.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for MarkBaseDavFs {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: Arc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DavFileSystem for MarkBaseDavFs {
|
||||
fn open<'a>(
|
||||
&'a self,
|
||||
path: &'a DavPath,
|
||||
_options: OpenOptions,
|
||||
) -> FsFuture<'a, Box<dyn DavFile>> {
|
||||
let node_id = self.resolve_path(path);
|
||||
|
||||
match node_id {
|
||||
Some(id) => {
|
||||
let conn = self.inner.sqlite.lock().unwrap();
|
||||
|
||||
let result = conn.query_row(
|
||||
"SELECT node_id, label, node_type, file_size, aliases_json
|
||||
FROM file_nodes WHERE node_id = ?",
|
||||
[&id],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
row.get::<_, Option<i64>>(3)?,
|
||||
row.get::<_, Option<String>>(4)?,
|
||||
))
|
||||
},
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok((_node_id, _label, node_type, _file_size, aliases_json)) => {
|
||||
if node_type == "folder" {
|
||||
Box::pin(std::future::ready(Err(FsError::Forbidden)))
|
||||
} else {
|
||||
let file_path = self.extract_file_path(&aliases_json);
|
||||
match file_path {
|
||||
Some(path) => match std::fs::read(&path) {
|
||||
Ok(data) => Box::pin(std::future::ready(Ok(Box::new(
|
||||
MarkBaseDavFile::new(data),
|
||||
)
|
||||
as Box<dyn DavFile>))),
|
||||
Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))),
|
||||
},
|
||||
None => Box::pin(std::future::ready(Err(FsError::NotFound))),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))),
|
||||
}
|
||||
}
|
||||
None => Box::pin(std::future::ready(Err(FsError::NotFound))),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_dir<'a>(
|
||||
&'a self,
|
||||
path: &'a DavPath,
|
||||
_meta: dav_server::fs::ReadDirMeta,
|
||||
) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
|
||||
let node_id = self.resolve_path(path);
|
||||
|
||||
match node_id {
|
||||
Some(id) => {
|
||||
let conn = self.inner.sqlite.lock().unwrap();
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT node_id, label, node_type, file_size
|
||||
FROM file_nodes WHERE parent_id = ?",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let entries = stmt
|
||||
.query_map([&id], |row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
row.get::<_, Option<i64>>(3)?,
|
||||
))
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut results: Vec<Box<dyn DavDirEntry>> = Vec::new();
|
||||
for entry in entries {
|
||||
match entry {
|
||||
Ok((node_id, label, node_type, file_size)) => {
|
||||
results.push(Box::new(MarkBaseDavDirEntry::from_file_node(
|
||||
&node_id, &label, &node_type, file_size,
|
||||
)) as Box<dyn DavDirEntry>);
|
||||
}
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
let stream = stream::iter(results.into_iter().map(Ok));
|
||||
Box::pin(std::future::ready(Ok(
|
||||
Box::pin(stream) as FsStream<Box<dyn DavDirEntry>>
|
||||
)))
|
||||
}
|
||||
None => Box::pin(std::future::ready(Err(FsError::NotFound))),
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
|
||||
let node_id = self.resolve_path(path);
|
||||
|
||||
match node_id {
|
||||
Some(id) => {
|
||||
let conn = self.inner.sqlite.lock().unwrap();
|
||||
|
||||
let result = conn.query_row(
|
||||
"SELECT node_id, label, node_type, file_size, aliases_json
|
||||
FROM file_nodes WHERE node_id = ?",
|
||||
[&id],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
row.get::<_, Option<i64>>(3)?,
|
||||
row.get::<_, Option<String>>(4)?,
|
||||
))
|
||||
},
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok((_node_id, _label, node_type, file_size, aliases_json)) => {
|
||||
let size = if node_type == "folder" {
|
||||
0u64
|
||||
} else {
|
||||
match file_size {
|
||||
Some(s) => s as u64,
|
||||
None => {
|
||||
let file_path = self.extract_file_path(&aliases_json);
|
||||
match file_path {
|
||||
Some(path) => {
|
||||
std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0)
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let metadata = MarkBaseDavMetaData::new(size, node_type == "folder");
|
||||
Box::pin(std::future::ready(Ok(
|
||||
Box::new(metadata) as Box<dyn DavMetaData>
|
||||
)))
|
||||
}
|
||||
Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))),
|
||||
}
|
||||
}
|
||||
None => Box::pin(std::future::ready(Err(FsError::NotFound))),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user