Test Gitea Runner functionality
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use anyhow::{Result, Error};
|
||||
|
||||
pub struct FuseOperations<'a> {
|
||||
fs: &'a MarkBaseFs,
|
||||
}
|
||||
|
||||
struct QueryNodeResult {
|
||||
node_id: String,
|
||||
label: String,
|
||||
node_type: String,
|
||||
file_size: Option<i64>,
|
||||
parent_id: Option<String>,
|
||||
created_at: Option<i64>,
|
||||
updated_at: Option<i64>,
|
||||
}
|
||||
|
||||
impl<'a> FuseOperations<'a> {
|
||||
pub fn new(fs: &'a MarkBaseFs) -> Self {
|
||||
FuseOperations { fs }
|
||||
}
|
||||
|
||||
pub fn getattr(&self, ino: u64) -> Result<FileAttr> {
|
||||
let uuid = MarkBaseFs::ino_to_uuid(ino);
|
||||
|
||||
let node = self.query_node(&uuid)?;
|
||||
|
||||
let kind = match node.node_type.as_str() {
|
||||
"folder" => FileKind::Directory,
|
||||
"file" => FileKind::RegularFile,
|
||||
_ => FileKind::RegularFile,
|
||||
};
|
||||
|
||||
let size = if kind == FileKind::RegularFile {
|
||||
node.file_size.unwrap_or(0) as u64
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
Ok(FileAttr {
|
||||
ino,
|
||||
size,
|
||||
mode: if kind == FileKind::Directory { 0o755 } else { 0o644 },
|
||||
nlink: if kind == FileKind::Directory { 2 } else { 1 },
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
atime: node.updated_at.unwrap_or(0) as u64,
|
||||
mtime: node.updated_at.unwrap_or(0) as u64,
|
||||
ctime: node.created_at.unwrap_or(0) as u64,
|
||||
kind,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn readdir(&self, ino: u64) -> Result<Vec<(u64, String, FileKind)>> {
|
||||
let uuid = MarkBaseFs::ino_to_uuid(ino);
|
||||
|
||||
let children = self.query_children(&uuid)?;
|
||||
|
||||
let entries: Vec<(u64, String, FileKind)> = children
|
||||
.into_iter()
|
||||
.map(|node| {
|
||||
let child_ino = MarkBaseFs::uuid_to_ino(&node.node_id);
|
||||
let kind = match node.node_type.as_str() {
|
||||
"folder" => FileKind::Directory,
|
||||
"file" => FileKind::RegularFile,
|
||||
_ => FileKind::RegularFile,
|
||||
};
|
||||
(child_ino, node.label, kind)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
pub fn read(&self, ino: u64, offset: u64, size: u32) -> Result<Vec<u8>> {
|
||||
let uuid = MarkBaseFs::ino_to_uuid(ino);
|
||||
|
||||
let path = self.get_file_path(&uuid)?;
|
||||
|
||||
if !path.exists() {
|
||||
return Err(Error::msg("File not found"));
|
||||
}
|
||||
|
||||
let mut file = File::open(&path)?;
|
||||
file.seek(SeekFrom::Start(offset))?;
|
||||
|
||||
let mut buffer = vec![0u8; size as usize];
|
||||
let bytes_read = file.read(&mut buffer)?;
|
||||
buffer.truncate(bytes_read);
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
fn query_node(&self, uuid: &str) -> Result<QueryNodeResult> {
|
||||
use rusqlite::Connection;
|
||||
|
||||
let db_path = self.fs.get_db_path();
|
||||
let conn = Connection::open(db_path)?;
|
||||
|
||||
let node = conn.query_row(
|
||||
"SELECT node_id, label, node_type, file_size, parent_id, created_at, updated_at
|
||||
FROM file_nodes
|
||||
WHERE node_id = ?",
|
||||
[uuid],
|
||||
|row| {
|
||||
Ok(QueryNodeResult {
|
||||
node_id: row.get::<_, String>(0)?,
|
||||
label: row.get::<_, String>(1)?,
|
||||
node_type: row.get::<_, String>(2)?,
|
||||
file_size: row.get::<_, Option<i64>>(3)?,
|
||||
parent_id: row.get::<_, Option<String>>(4)?,
|
||||
created_at: row.get::<_, Option<i64>>(5)?,
|
||||
updated_at: row.get::<_, Option<i64>>(6)?,
|
||||
})
|
||||
}
|
||||
)?;
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
fn query_children(&self, parent_uuid: &str) -> Result<Vec<QueryNodeResult>> {
|
||||
use rusqlite::Connection;
|
||||
|
||||
let db_path = self.fs.get_db_path();
|
||||
let conn = Connection::open(db_path)?;
|
||||
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT node_id, label, node_type, file_size, parent_id, created_at, updated_at
|
||||
FROM file_nodes
|
||||
WHERE parent_id = ?
|
||||
ORDER BY sort_order, label"
|
||||
)?;
|
||||
|
||||
let children = stmt.query_map([parent_uuid], |row| {
|
||||
Ok(QueryNodeResult {
|
||||
node_id: row.get::<_, String>(0)?,
|
||||
label: row.get::<_, String>(1)?,
|
||||
node_type: row.get::<_, String>(2)?,
|
||||
file_size: row.get::<_, Option<i64>>(3)?,
|
||||
parent_id: row.get::<_, Option<String>>(4)?,
|
||||
created_at: row.get::<_, Option<i64>>(5)?,
|
||||
updated_at: row.get::<_, Option<i64>>(6)?,
|
||||
})
|
||||
})?.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(children)
|
||||
}
|
||||
|
||||
fn get_file_path(&self, uuid: &str) -> Result<PathBuf> {
|
||||
use rusqlite::Connection;
|
||||
|
||||
let db_path = self.fs.get_db_path();
|
||||
let conn = Connection::open(db_path)?;
|
||||
|
||||
let path_str = conn.query_row(
|
||||
"SELECT location FROM file_locations WHERE file_uuid = ?",
|
||||
[uuid],
|
||||
|row| row.get::<_, String>(0)
|
||||
)?;
|
||||
|
||||
Ok(PathBuf::from(path_str))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::fuse::backend::BackendType;
|
||||
|
||||
#[test]
|
||||
fn test_fuse_operations_creation() {
|
||||
let db_path = PathBuf::from("data/users/warren.sqlite");
|
||||
let fs = MarkBaseFs::new("warren".to_string(), db_path, BackendType::Fskit);
|
||||
let ops = FuseOperations::new(&fs);
|
||||
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uuid_roundtrip() {
|
||||
let uuid = "8b1ede3cd6970f02fa85b8e34b682caf";
|
||||
let ino = MarkBaseFs::uuid_to_ino(uuid);
|
||||
|
||||
// Just verify the conversion produces a valid inode number
|
||||
assert!(ino > 0);
|
||||
|
||||
// And that we can convert back
|
||||
let recovered = MarkBaseFs::ino_to_uuid(ino);
|
||||
assert!(!recovered.is_empty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user