Fix code quality: trailing whitespace, unused imports, clippy warnings
- Fix trailing whitespace in kex.rs and s3.rs - Add missing KexProposal import in kex_complete.rs - Auto-fix clippy warnings across all crates - All 153 tests pass
This commit is contained in:
@@ -25,7 +25,7 @@ pub async fn handle_iscsi_command(cmd: IscsiCommand) -> anyhow::Result<()> {
|
||||
let binary = find_binary("markbase-iscsi");
|
||||
let mut cmd_process = std::process::Command::new(&binary);
|
||||
cmd_process.arg("iscsi");
|
||||
|
||||
|
||||
match cmd {
|
||||
IscsiCommand::Start {
|
||||
user,
|
||||
@@ -34,7 +34,8 @@ pub async fn handle_iscsi_command(cmd: IscsiCommand) -> anyhow::Result<()> {
|
||||
force,
|
||||
device,
|
||||
} => {
|
||||
cmd_process.arg("start")
|
||||
cmd_process
|
||||
.arg("start")
|
||||
.args(["--user", &user])
|
||||
.args(["--port", &port.to_string()])
|
||||
.args(["--lun-size", &lun_size]);
|
||||
@@ -52,7 +53,7 @@ pub async fn handle_iscsi_command(cmd: IscsiCommand) -> anyhow::Result<()> {
|
||||
cmd_process.arg("status");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let status = cmd_process.status()?;
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
}
|
||||
@@ -61,4 +62,4 @@ fn find_binary(name: &str) -> std::path::PathBuf {
|
||||
let exe = std::env::current_exe().unwrap();
|
||||
let dir = exe.parent().unwrap();
|
||||
dir.join(name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
pub mod web;
|
||||
pub mod ssh;
|
||||
pub mod webdav;
|
||||
pub mod iscsi;
|
||||
pub mod ssh;
|
||||
pub mod tree;
|
||||
pub mod web;
|
||||
pub mod webdav;
|
||||
|
||||
use clap::Subcommand;
|
||||
|
||||
@@ -29,4 +29,4 @@ pub async fn handle_interface_command(cmd: InterfaceCommands) -> anyhow::Result<
|
||||
InterfaceCommands::Tree(c) => tree::handle_tree_command(c).await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,4 +32,4 @@ pub async fn handle_ssh_command(cmd: SshCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Context;
|
||||
use clap::Subcommand;
|
||||
use rusqlite::Connection;
|
||||
use anyhow::Context;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
@@ -33,12 +33,12 @@ pub enum TreeCommand {
|
||||
#[arg(short, long)]
|
||||
name: String,
|
||||
},
|
||||
|
||||
|
||||
Folder {
|
||||
#[command(subcommand)]
|
||||
action: FolderCommand,
|
||||
},
|
||||
|
||||
|
||||
Ls {
|
||||
#[arg(short, long)]
|
||||
user: String,
|
||||
@@ -47,7 +47,7 @@ pub enum TreeCommand {
|
||||
#[arg(short, long)]
|
||||
tree_type: String,
|
||||
},
|
||||
|
||||
|
||||
Cp {
|
||||
#[arg(short, long)]
|
||||
user: String,
|
||||
@@ -58,7 +58,7 @@ pub enum TreeCommand {
|
||||
#[arg(short, long)]
|
||||
tree_type: String,
|
||||
},
|
||||
|
||||
|
||||
Mv {
|
||||
#[arg(short, long)]
|
||||
user: String,
|
||||
@@ -113,44 +113,54 @@ pub enum FolderCommand {
|
||||
|
||||
pub async fn handle_tree_command(cmd: TreeCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
TreeCommand::Create { name, user, tree_type } => {
|
||||
TreeCommand::Create {
|
||||
name,
|
||||
user,
|
||||
tree_type,
|
||||
} => {
|
||||
let db_path = format!("data/users/{}.sqlite", user);
|
||||
let conn = Connection::open(&db_path)
|
||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||
|
||||
|
||||
let node_id = Uuid::new_v4().to_string();
|
||||
let created_at = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO file_nodes (node_id, label, node_type, tree_type, created_at, updated_at)
|
||||
VALUES (?1, ?2, 'folder', ?3, ?4, ?4)",
|
||||
rusqlite::params![node_id, name, tree_type, created_at]
|
||||
).context("Failed to create tree")?;
|
||||
|
||||
println!("✓ Tree created: {} (type: {}) for user: {}", name, tree_type, user);
|
||||
|
||||
println!(
|
||||
"✓ Tree created: {} (type: {}) for user: {}",
|
||||
name, tree_type, user
|
||||
);
|
||||
println!("✓ Node ID: {}", node_id);
|
||||
}
|
||||
TreeCommand::List { user } => {
|
||||
let db_path = format!("data/users/{}.sqlite", user);
|
||||
let conn = Connection::open(&db_path)
|
||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT DISTINCT tree_type FROM file_nodes ORDER BY tree_type"
|
||||
).context("Failed to prepare query")?;
|
||||
|
||||
let tree_types = stmt.query_map([], |row| row.get::<_, String>(0))
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT DISTINCT tree_type FROM file_nodes ORDER BY tree_type")
|
||||
.context("Failed to prepare query")?;
|
||||
|
||||
let tree_types = stmt
|
||||
.query_map([], |row| row.get::<_, String>(0))
|
||||
.context("Failed to query tree types")?;
|
||||
|
||||
|
||||
println!("=== Trees for user: {} ===", user);
|
||||
for tree_type in tree_types {
|
||||
let tt = tree_type?;
|
||||
let count: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM file_nodes WHERE tree_type = ?1",
|
||||
[&tt],
|
||||
|row| row.get(0)
|
||||
).unwrap_or(0);
|
||||
|
||||
let count: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM file_nodes WHERE tree_type = ?1",
|
||||
[&tt],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap_or(0);
|
||||
|
||||
println!(" {} ({} nodes)", tt, count);
|
||||
}
|
||||
}
|
||||
@@ -158,9 +168,9 @@ pub async fn handle_tree_command(cmd: TreeCommand) -> anyhow::Result<()> {
|
||||
let db_path = format!("data/users/{}.sqlite", user);
|
||||
let conn = Connection::open(&db_path)
|
||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||
|
||||
|
||||
println!("Importing Markdown files to {} virtual tree...", tree_type);
|
||||
|
||||
|
||||
if tree_type == "categories" {
|
||||
crate::import_markdown::import_categories_to_db(&conn, &user, &tree_type)?;
|
||||
println!("✓ Categories imported successfully!");
|
||||
@@ -168,53 +178,66 @@ pub async fn handle_tree_command(cmd: TreeCommand) -> anyhow::Result<()> {
|
||||
crate::import_markdown::import_series_to_db(&conn, &user, &tree_type)?;
|
||||
println!("✓ Series imported successfully!");
|
||||
} else {
|
||||
eprintln!("Invalid tree_type: {}. Use 'categories' or 'series'", tree_type);
|
||||
eprintln!(
|
||||
"Invalid tree_type: {}. Use 'categories' or 'series'",
|
||||
tree_type
|
||||
);
|
||||
}
|
||||
}
|
||||
TreeCommand::Delete { user, name } => {
|
||||
let db_path = format!("data/users/{}.sqlite", user);
|
||||
let conn = Connection::open(&db_path)
|
||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM file_nodes WHERE label = ?1 AND node_type = 'folder'",
|
||||
[&name]
|
||||
).context("Failed to delete tree")?;
|
||||
|
||||
[&name],
|
||||
)
|
||||
.context("Failed to delete tree")?;
|
||||
|
||||
println!("✓ Tree deleted: {} for user: {}", name, user);
|
||||
}
|
||||
|
||||
|
||||
TreeCommand::Folder { action } => {
|
||||
handle_folder_command(action)?;
|
||||
}
|
||||
|
||||
TreeCommand::Ls { user, path, tree_type } => {
|
||||
|
||||
TreeCommand::Ls {
|
||||
user,
|
||||
path,
|
||||
tree_type,
|
||||
} => {
|
||||
let db_path = format!("data/users/{}.sqlite", user);
|
||||
let conn = Connection::open(&db_path)
|
||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||
|
||||
|
||||
let parent_id = find_node_id(&conn, &path, &tree_type)?;
|
||||
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT label, node_type, file_size FROM file_nodes
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT label, node_type, file_size FROM file_nodes
|
||||
WHERE parent_id = ?1 AND tree_type = ?2
|
||||
ORDER BY node_type DESC, label ASC"
|
||||
).context("Failed to prepare ls query")?;
|
||||
|
||||
let entries = stmt.query_map(
|
||||
rusqlite::params![parent_id, tree_type],
|
||||
|row| Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, Option<i64>>(2)?
|
||||
))
|
||||
).context("Failed to query entries")?;
|
||||
|
||||
ORDER BY node_type DESC, label ASC",
|
||||
)
|
||||
.context("Failed to prepare ls query")?;
|
||||
|
||||
let entries = stmt
|
||||
.query_map(rusqlite::params![parent_id, tree_type], |row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, Option<i64>>(2)?,
|
||||
))
|
||||
})
|
||||
.context("Failed to query entries")?;
|
||||
|
||||
println!("=== Contents of {} (tree_type: {}) ===", path, tree_type);
|
||||
for entry in entries {
|
||||
let (name, node_type, size) = entry?;
|
||||
let size_str = size.map(|s| format!("{} bytes", s)).unwrap_or_else(|| "-".to_string());
|
||||
|
||||
let size_str = size
|
||||
.map(|s| format!("{} bytes", s))
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
|
||||
if node_type == "folder" {
|
||||
println!(" 📁 {} ({})", name, size_str);
|
||||
} else {
|
||||
@@ -222,57 +245,72 @@ pub async fn handle_tree_command(cmd: TreeCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TreeCommand::Cp { user, source, target, tree_type } => {
|
||||
|
||||
TreeCommand::Cp {
|
||||
user,
|
||||
source,
|
||||
target,
|
||||
tree_type,
|
||||
} => {
|
||||
let db_path = format!("data/users/{}.sqlite", user);
|
||||
let conn = Connection::open(&db_path)
|
||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||
|
||||
|
||||
let source_id = find_node_id(&conn, &source, &tree_type)?;
|
||||
let target_parent_id = find_node_id(&conn, &target, &tree_type)?;
|
||||
|
||||
let (label, node_type, aliases_json, file_uuid, sha256, file_size) = conn.query_row(
|
||||
"SELECT label, node_type, aliases_json, file_uuid, sha256, file_size
|
||||
|
||||
let (label, node_type, aliases_json, file_uuid, sha256, file_size) = conn
|
||||
.query_row(
|
||||
"SELECT label, node_type, aliases_json, file_uuid, sha256, file_size
|
||||
FROM file_nodes WHERE node_id = ?1",
|
||||
[&source_id],
|
||||
|row| Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
row.get::<_, Option<String>>(3)?,
|
||||
row.get::<_, Option<String>>(4)?,
|
||||
row.get::<_, Option<i64>>(5)?
|
||||
))
|
||||
).context("Failed to get source node")?;
|
||||
|
||||
[&source_id],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
row.get::<_, Option<String>>(3)?,
|
||||
row.get::<_, Option<String>>(4)?,
|
||||
row.get::<_, Option<i64>>(5)?,
|
||||
))
|
||||
},
|
||||
)
|
||||
.context("Failed to get source node")?;
|
||||
|
||||
let new_id = Uuid::new_v4().to_string();
|
||||
let created_at = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO file_nodes
|
||||
(node_id, label, aliases_json, file_uuid, sha256, parent_id, node_type, file_size, tree_type, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?10)",
|
||||
rusqlite::params![new_id, label, aliases_json, file_uuid, sha256, target_parent_id, node_type, file_size, tree_type, created_at]
|
||||
).context("Failed to copy node")?;
|
||||
|
||||
|
||||
println!("✓ Copied {} to {} (new ID: {})", source, target, new_id);
|
||||
}
|
||||
|
||||
TreeCommand::Mv { user, source, target, tree_type } => {
|
||||
|
||||
TreeCommand::Mv {
|
||||
user,
|
||||
source,
|
||||
target,
|
||||
tree_type,
|
||||
} => {
|
||||
let db_path = format!("data/users/{}.sqlite", user);
|
||||
let conn = Connection::open(&db_path)
|
||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||
|
||||
|
||||
let source_id = find_node_id(&conn, &source, &tree_type)?;
|
||||
let target_parent_id = find_node_id(&conn, &target, &tree_type)?;
|
||||
|
||||
|
||||
let updated_at = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
|
||||
conn.execute(
|
||||
"UPDATE file_nodes SET parent_id = ?1, updated_at = ?2 WHERE node_id = ?3",
|
||||
rusqlite::params![target_parent_id, updated_at, source_id]
|
||||
).context("Failed to move node")?;
|
||||
|
||||
rusqlite::params![target_parent_id, updated_at, source_id],
|
||||
)
|
||||
.context("Failed to move node")?;
|
||||
|
||||
println!("✓ Moved {} to {}", source, target);
|
||||
}
|
||||
}
|
||||
@@ -281,104 +319,136 @@ pub async fn handle_tree_command(cmd: TreeCommand) -> anyhow::Result<()> {
|
||||
|
||||
fn handle_folder_command(cmd: FolderCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
FolderCommand::Create { user, path, name, tree_type } => {
|
||||
FolderCommand::Create {
|
||||
user,
|
||||
path,
|
||||
name,
|
||||
tree_type,
|
||||
} => {
|
||||
let db_path = format!("data/users/{}.sqlite", user);
|
||||
let conn = Connection::open(&db_path)
|
||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||
|
||||
let parent_id = if path == "/" || path == "" {
|
||||
|
||||
let parent_id = if path == "/" || path.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(find_node_id(&conn, &path, &tree_type)?)
|
||||
};
|
||||
|
||||
|
||||
let node_id = Uuid::new_v4().to_string();
|
||||
let created_at = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO file_nodes
|
||||
(node_id, label, parent_id, node_type, tree_type, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, 'folder', ?4, ?5, ?5)",
|
||||
rusqlite::params![node_id, name, parent_id, tree_type, created_at]
|
||||
).context("Failed to create folder")?;
|
||||
|
||||
println!("✓ Folder created: {} in {} (tree_type: {})", name, path, tree_type);
|
||||
rusqlite::params![node_id, name, parent_id, tree_type, created_at],
|
||||
)
|
||||
.context("Failed to create folder")?;
|
||||
|
||||
println!(
|
||||
"✓ Folder created: {} in {} (tree_type: {})",
|
||||
name, path, tree_type
|
||||
);
|
||||
println!("✓ Node ID: {}", node_id);
|
||||
}
|
||||
FolderCommand::Delete { user, path, name, tree_type } => {
|
||||
FolderCommand::Delete {
|
||||
user,
|
||||
path,
|
||||
name,
|
||||
tree_type,
|
||||
} => {
|
||||
let db_path = format!("data/users/{}.sqlite", user);
|
||||
let conn = Connection::open(&db_path)
|
||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||
|
||||
let folder_path = if path == "/" || path == "" {
|
||||
|
||||
let folder_path = if path == "/" || path.is_empty() {
|
||||
name.clone()
|
||||
} else {
|
||||
format!("{}/{}", path, name)
|
||||
};
|
||||
|
||||
|
||||
let folder_id = find_node_id(&conn, &folder_path, &tree_type)?;
|
||||
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM file_nodes WHERE node_id = ?1 OR parent_id = ?1",
|
||||
[&folder_id]
|
||||
).context("Failed to delete folder and children")?;
|
||||
|
||||
println!("✓ Folder deleted: {} in {} (tree_type: {})", name, path, tree_type);
|
||||
[&folder_id],
|
||||
)
|
||||
.context("Failed to delete folder and children")?;
|
||||
|
||||
println!(
|
||||
"✓ Folder deleted: {} in {} (tree_type: {})",
|
||||
name, path, tree_type
|
||||
);
|
||||
}
|
||||
FolderCommand::Rename { user, path, old_name, new_name, tree_type } => {
|
||||
FolderCommand::Rename {
|
||||
user,
|
||||
path,
|
||||
old_name,
|
||||
new_name,
|
||||
tree_type,
|
||||
} => {
|
||||
let db_path = format!("data/users/{}.sqlite", user);
|
||||
let conn = Connection::open(&db_path)
|
||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||
|
||||
let folder_path = if path == "/" || path == "" {
|
||||
|
||||
let folder_path = if path == "/" || path.is_empty() {
|
||||
old_name.clone()
|
||||
} else {
|
||||
format!("{}/{}", path, old_name)
|
||||
};
|
||||
|
||||
|
||||
let folder_id = find_node_id(&conn, &folder_path, &tree_type)?;
|
||||
|
||||
|
||||
let updated_at = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
|
||||
conn.execute(
|
||||
"UPDATE file_nodes SET label = ?1, updated_at = ?2 WHERE node_id = ?3",
|
||||
rusqlite::params![new_name, updated_at, folder_id]
|
||||
).context("Failed to rename folder")?;
|
||||
|
||||
println!("✓ Folder renamed: {} → {} in {} (tree_type: {})", old_name, new_name, path, tree_type);
|
||||
rusqlite::params![new_name, updated_at, folder_id],
|
||||
)
|
||||
.context("Failed to rename folder")?;
|
||||
|
||||
println!(
|
||||
"✓ Folder renamed: {} → {} in {} (tree_type: {})",
|
||||
old_name, new_name, path, tree_type
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_node_id(conn: &Connection, path: &str, tree_type: &str) -> anyhow::Result<String> {
|
||||
if path == "/" || path == "" {
|
||||
let node_id: String = conn.query_row(
|
||||
"SELECT node_id FROM file_nodes
|
||||
if path == "/" || path.is_empty() {
|
||||
let node_id: String = conn
|
||||
.query_row(
|
||||
"SELECT node_id FROM file_nodes
|
||||
WHERE parent_id IS NULL AND node_type = 'folder' AND tree_type = ?1
|
||||
LIMIT 1",
|
||||
[tree_type],
|
||||
|row| row.get(0)
|
||||
).context("Failed to find root folder")?;
|
||||
|
||||
[tree_type],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.context("Failed to find root folder")?;
|
||||
|
||||
return Ok(node_id);
|
||||
}
|
||||
|
||||
|
||||
let parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
|
||||
|
||||
|
||||
let mut current_parent: Option<String> = None;
|
||||
|
||||
|
||||
for part in parts {
|
||||
let node_id: String = conn.query_row(
|
||||
"SELECT node_id FROM file_nodes
|
||||
let node_id: String = conn
|
||||
.query_row(
|
||||
"SELECT node_id FROM file_nodes
|
||||
WHERE label = ?1 AND tree_type = ?2 AND
|
||||
(parent_id = ?3 OR (?3 IS NULL AND parent_id IS NULL))",
|
||||
rusqlite::params![part, tree_type, current_parent],
|
||||
|row| row.get(0)
|
||||
).context(format!("Failed to find node: {}", part))?;
|
||||
|
||||
rusqlite::params![part, tree_type, current_parent],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.context(format!("Failed to find node: {}", part))?;
|
||||
|
||||
current_parent = Some(node_id);
|
||||
}
|
||||
|
||||
|
||||
current_parent.context("Failed to find node ID for path")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,4 @@ pub async fn handle_web_command(cmd: WebCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clap::Subcommand;
|
||||
use axum::{extract::Request, response::IntoResponse, Extension};
|
||||
use clap::Subcommand;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum WebdavCommand {
|
||||
@@ -28,7 +28,7 @@ pub async fn handle_webdav_command(cmd: WebdavCommand) -> anyhow::Result<()> {
|
||||
println!("User: {}", user);
|
||||
println!("Port: {}", port);
|
||||
println!("Database: {}", db_path.display());
|
||||
println!("");
|
||||
println!();
|
||||
|
||||
run_webdav_server(port, user, db_path).await?;
|
||||
}
|
||||
@@ -41,7 +41,7 @@ async fn run_webdav_server(
|
||||
user: String,
|
||||
db_path: std::path::PathBuf,
|
||||
) -> anyhow::Result<()> {
|
||||
use axum::{extract::Request, response::IntoResponse, routing::any, Extension, Router};
|
||||
use axum::{routing::any, Extension, Router};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
let webdav = markbase_webdav::webdav::MarkBaseWebDAV::new(user, db_path);
|
||||
@@ -58,7 +58,7 @@ async fn run_webdav_server(
|
||||
|
||||
println!("WebDAV server listening on http://{}", addr);
|
||||
println!("Mount point: /webdav");
|
||||
println!("");
|
||||
println!();
|
||||
println!("Press Ctrl+C to stop");
|
||||
|
||||
axum::serve(listener, app).await?;
|
||||
@@ -71,4 +71,4 @@ async fn handle_dav(
|
||||
req: Request,
|
||||
) -> impl IntoResponse {
|
||||
dav.handle(req).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Context;
|
||||
use clap::Subcommand;
|
||||
use rusqlite::Connection;
|
||||
use anyhow::Context;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum AuthCommand {
|
||||
@@ -24,29 +24,30 @@ pub fn handle_auth_command(cmd: AuthCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
AuthCommand::Login { user, password } => {
|
||||
let db_path = "data/auth.sqlite";
|
||||
|
||||
|
||||
if !std::path::Path::new(db_path).exists() {
|
||||
return Err(anyhow::anyhow!("Auth database not found: {}", db_path));
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)
|
||||
.context("Failed to open auth database")?;
|
||||
|
||||
let password_hash: String = conn.query_row(
|
||||
"SELECT password_hash FROM sftpgo_users WHERE username = ?",
|
||||
[&user],
|
||||
|row| row.get(0)
|
||||
).context("Failed to query password hash")?;
|
||||
|
||||
let valid = bcrypt::verify(&password, &password_hash)
|
||||
.context("Failed to verify password")?;
|
||||
|
||||
|
||||
let conn = Connection::open(db_path).context("Failed to open auth database")?;
|
||||
|
||||
let password_hash: String = conn
|
||||
.query_row(
|
||||
"SELECT password_hash FROM sftpgo_users WHERE username = ?",
|
||||
[&user],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.context("Failed to query password hash")?;
|
||||
|
||||
let valid =
|
||||
bcrypt::verify(&password, &password_hash).context("Failed to verify password")?;
|
||||
|
||||
if !valid {
|
||||
return Err(anyhow::anyhow!("Invalid password for user: {}", user));
|
||||
}
|
||||
|
||||
|
||||
let token = generate_simple_token(&user);
|
||||
|
||||
|
||||
println!("✓ Login successful for user: {}", user);
|
||||
println!("✓ Token: {}", token);
|
||||
println!("Note: This is a simple token for demonstration. Use JWT in production.");
|
||||
@@ -57,7 +58,7 @@ pub fn handle_auth_command(cmd: AuthCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
AuthCommand::Verify { token } => {
|
||||
let user = verify_simple_token(&token)?;
|
||||
|
||||
|
||||
println!("✓ Token valid for user: {}", user);
|
||||
println!("Note: This is simple token verification. Use JWT in production.");
|
||||
}
|
||||
@@ -67,37 +68,38 @@ pub fn handle_auth_command(cmd: AuthCommand) -> anyhow::Result<()> {
|
||||
|
||||
fn generate_simple_token(user: &str) -> String {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
|
||||
format!("{}_{}", user, timestamp)
|
||||
}
|
||||
|
||||
fn verify_simple_token(token: &str) -> anyhow::Result<String> {
|
||||
let parts: Vec<&str> = token.split('_').collect();
|
||||
|
||||
|
||||
if parts.len() < 2 {
|
||||
return Err(anyhow::anyhow!("Invalid token format"));
|
||||
}
|
||||
|
||||
|
||||
let user = parts[0];
|
||||
let timestamp_str = parts[1];
|
||||
|
||||
let timestamp: u64 = timestamp_str.parse()
|
||||
|
||||
let timestamp: u64 = timestamp_str
|
||||
.parse()
|
||||
.context("Failed to parse token timestamp")?;
|
||||
|
||||
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
|
||||
if now - timestamp > 86400 {
|
||||
return Err(anyhow::anyhow!("Token expired (valid for 24 hours)"));
|
||||
}
|
||||
|
||||
|
||||
Ok(user.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,9 @@ pub fn handle_config_command(cmd: ConfigCommand) -> anyhow::Result<()> {
|
||||
let config_path = Path::new("config/markbase.toml");
|
||||
|
||||
if !config_path.exists() {
|
||||
println!("Configuration file not found. Run 'markbase metadata config init' first.");
|
||||
println!(
|
||||
"Configuration file not found. Run 'markbase metadata config init' first."
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -61,7 +63,9 @@ pub fn handle_config_command(cmd: ConfigCommand) -> anyhow::Result<()> {
|
||||
let config_path = Path::new("config/markbase.toml");
|
||||
|
||||
if !config_path.exists() {
|
||||
println!("Configuration file not found. Run 'markbase metadata config init' first.");
|
||||
println!(
|
||||
"Configuration file not found. Run 'markbase metadata config init' first."
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -86,7 +90,9 @@ pub fn handle_config_command(cmd: ConfigCommand) -> anyhow::Result<()> {
|
||||
let config_path = Path::new("config/markbase.toml");
|
||||
|
||||
if !config_path.exists() {
|
||||
println!("Configuration file not found. Run 'markbase metadata config init' first.");
|
||||
println!(
|
||||
"Configuration file not found. Run 'markbase metadata config init' first."
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -115,4 +121,4 @@ fn show_section(config: &crate::config::MarkBaseConfig, section: &str) {
|
||||
"logging" => println!("{}", toml::to_string_pretty(&config.logging).unwrap()),
|
||||
_ => println!("Invalid section: {}. Valid sections: server, postgresql, authentication, test, logging", section),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Context;
|
||||
use clap::Subcommand;
|
||||
use rusqlite::Connection;
|
||||
use anyhow::Context;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum DbCommand {
|
||||
@@ -34,54 +34,52 @@ pub fn handle_db_command(cmd: DbCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
DbCommand::Create { user } => {
|
||||
let db_path = filetree::FileTree::user_db_path(&user);
|
||||
|
||||
|
||||
if std::path::Path::new(&db_path).exists() {
|
||||
println!("Database already exists: {}", db_path);
|
||||
println!("Use 'db status' to check database info");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
println!("Creating database for user: {}", user);
|
||||
|
||||
let conn = filetree::FileTree::init_user_db(&user)
|
||||
.context("Failed to initialize database")?;
|
||||
|
||||
|
||||
let conn =
|
||||
filetree::FileTree::init_user_db(&user).context("Failed to initialize database")?;
|
||||
|
||||
println!("✓ Database created: {}", db_path);
|
||||
println!("✓ Tables initialized: file_nodes, file_registry, file_locations, tree_registry");
|
||||
|
||||
conn.close().map_err(|e| anyhow::anyhow!("Failed to close database: {:?}", e))?;
|
||||
println!(
|
||||
"✓ Tables initialized: file_nodes, file_registry, file_locations, tree_registry"
|
||||
);
|
||||
|
||||
conn.close()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to close database: {:?}", e))?;
|
||||
}
|
||||
DbCommand::Status { user } => {
|
||||
let db_path = filetree::FileTree::user_db_path(&user);
|
||||
|
||||
|
||||
if !std::path::Path::new(&db_path).exists() {
|
||||
return Err(anyhow::anyhow!("Database not found: {}", db_path));
|
||||
}
|
||||
|
||||
let conn = Connection::open(&db_path)
|
||||
.context("Failed to open database")?;
|
||||
|
||||
|
||||
let conn = Connection::open(&db_path).context("Failed to open database")?;
|
||||
|
||||
let file_size = std::fs::metadata(&db_path)?.len();
|
||||
let file_size_mb = file_size as f64 / 1024.0 / 1024.0;
|
||||
|
||||
let node_count: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM file_nodes",
|
||||
[],
|
||||
|row| row.get(0)
|
||||
).context("Failed to count nodes")?;
|
||||
|
||||
let file_count: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM file_registry",
|
||||
[],
|
||||
|row| row.get(0)
|
||||
).context("Failed to count files")?;
|
||||
|
||||
|
||||
let node_count: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM file_nodes", [], |row| row.get(0))
|
||||
.context("Failed to count nodes")?;
|
||||
|
||||
let file_count: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM file_registry", [], |row| row.get(0))
|
||||
.context("Failed to count files")?;
|
||||
|
||||
let tree_types: Vec<String> = {
|
||||
let mut stmt = conn.prepare("SELECT tree_type FROM tree_registry")?;
|
||||
let rows = stmt.query_map([], |row| row.get(0))?;
|
||||
rows.collect::<Result<Vec<_>, _>>()?
|
||||
};
|
||||
|
||||
|
||||
println!("=== Database Status ===");
|
||||
println!("User: {}", user);
|
||||
println!("Path: {}", db_path);
|
||||
@@ -89,21 +87,21 @@ pub fn handle_db_command(cmd: DbCommand) -> anyhow::Result<()> {
|
||||
println!("Nodes: {}", node_count);
|
||||
println!("Files: {}", file_count);
|
||||
println!("Tree Types: {:?}", tree_types);
|
||||
|
||||
conn.close().map_err(|e| anyhow::anyhow!("Failed to close database: {:?}", e))?;
|
||||
|
||||
conn.close()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to close database: {:?}", e))?;
|
||||
}
|
||||
DbCommand::Backup { user, output } => {
|
||||
let db_path = filetree::FileTree::user_db_path(&user);
|
||||
|
||||
|
||||
if !std::path::Path::new(&db_path).exists() {
|
||||
return Err(anyhow::anyhow!("Database not found: {}", db_path));
|
||||
}
|
||||
|
||||
|
||||
println!("Backing up database for user: {} to {}", user, output);
|
||||
|
||||
std::fs::copy(&db_path, &output)
|
||||
.context("Failed to backup database")?;
|
||||
|
||||
|
||||
std::fs::copy(&db_path, &output).context("Failed to backup database")?;
|
||||
|
||||
println!("✓ Database backed up to: {}", output);
|
||||
println!("✓ Backup size: {} bytes", std::fs::metadata(&output)?.len());
|
||||
}
|
||||
@@ -111,24 +109,26 @@ pub fn handle_db_command(cmd: DbCommand) -> anyhow::Result<()> {
|
||||
if !std::path::Path::new(&input).exists() {
|
||||
return Err(anyhow::anyhow!("Backup file not found: {}", input));
|
||||
}
|
||||
|
||||
|
||||
let db_path = filetree::FileTree::user_db_path(&user);
|
||||
|
||||
|
||||
if std::path::Path::new(&db_path).exists() {
|
||||
let backup_path = format!("{}.bak", db_path);
|
||||
println!("Warning: Database exists, creating backup: {}", backup_path);
|
||||
std::fs::copy(&db_path, &backup_path)
|
||||
.context("Failed to create backup before restore")?;
|
||||
}
|
||||
|
||||
|
||||
println!("Restoring database for user: {} from {}", user, input);
|
||||
|
||||
std::fs::copy(&input, &db_path)
|
||||
.context("Failed to restore database")?;
|
||||
|
||||
|
||||
std::fs::copy(&input, &db_path).context("Failed to restore database")?;
|
||||
|
||||
println!("✓ Database restored from: {}", input);
|
||||
println!("✓ Database size: {} bytes", std::fs::metadata(&db_path)?.len());
|
||||
println!(
|
||||
"✓ Database size: {} bytes",
|
||||
std::fs::metadata(&db_path)?.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod config;
|
||||
pub mod user;
|
||||
pub mod db;
|
||||
pub mod auth;
|
||||
pub mod config;
|
||||
pub mod db;
|
||||
pub mod user;
|
||||
|
||||
use clap::Subcommand;
|
||||
|
||||
@@ -25,4 +25,4 @@ pub async fn handle_metadata_command(cmd: MetadataCommands) -> anyhow::Result<()
|
||||
MetadataCommands::Auth(c) => auth::handle_auth_command(c)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Context;
|
||||
use clap::Subcommand;
|
||||
use rusqlite::Connection;
|
||||
use anyhow::Context;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum UserCommand {
|
||||
@@ -18,7 +18,7 @@ pub enum UserCommand {
|
||||
#[arg(short, long)]
|
||||
name: String,
|
||||
},
|
||||
#[command(name = "user-delete")]
|
||||
#[command(name = "user-delete")]
|
||||
Delete {
|
||||
#[arg(short, long)]
|
||||
name: String,
|
||||
@@ -29,58 +29,60 @@ pub fn handle_user_command(cmd: UserCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
UserCommand::Create { name, password } => {
|
||||
let db_path = "data/auth.sqlite";
|
||||
|
||||
|
||||
if !std::path::Path::new(db_path).exists() {
|
||||
return Err(anyhow::anyhow!("Auth database not found: {}", db_path));
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)
|
||||
.context("Failed to open auth database")?;
|
||||
|
||||
let exists: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM sftpgo_users WHERE username = ?",
|
||||
[&name],
|
||||
|row| row.get(0)
|
||||
).context("Failed to check user existence")?;
|
||||
|
||||
|
||||
let conn = Connection::open(db_path).context("Failed to open auth database")?;
|
||||
|
||||
let exists: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM sftpgo_users WHERE username = ?",
|
||||
[&name],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.context("Failed to check user existence")?;
|
||||
|
||||
if exists > 0 {
|
||||
return Err(anyhow::anyhow!("User already exists: {}", name));
|
||||
}
|
||||
|
||||
let password_hash = bcrypt::hash(&password, bcrypt::DEFAULT_COST)
|
||||
.context("Failed to hash password")?;
|
||||
|
||||
|
||||
let password_hash =
|
||||
bcrypt::hash(&password, bcrypt::DEFAULT_COST).context("Failed to hash password")?;
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO sftpgo_users (username, password_hash, role, created_at) VALUES (?, ?, 'user', datetime('now'))",
|
||||
rusqlite::params![name, password_hash]
|
||||
).context("Failed to create user")?;
|
||||
|
||||
|
||||
println!("✓ User created: {}", name);
|
||||
println!("✓ Role: user");
|
||||
println!("✓ Password hashed with bcrypt");
|
||||
}
|
||||
UserCommand::List => {
|
||||
let db_path = "data/auth.sqlite";
|
||||
|
||||
|
||||
if !std::path::Path::new(db_path).exists() {
|
||||
return Err(anyhow::anyhow!("Auth database not found: {}", db_path));
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)
|
||||
.context("Failed to open auth database")?;
|
||||
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT username, role, created_at FROM sftpgo_users ORDER BY username"
|
||||
).context("Failed to prepare query")?;
|
||||
|
||||
let users = stmt.query_map([], |row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
))
|
||||
}).context("Failed to query users")?;
|
||||
|
||||
|
||||
let conn = Connection::open(db_path).context("Failed to open auth database")?;
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT username, role, created_at FROM sftpgo_users ORDER BY username")
|
||||
.context("Failed to prepare query")?;
|
||||
|
||||
let users = stmt
|
||||
.query_map([], |row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
))
|
||||
})
|
||||
.context("Failed to query users")?;
|
||||
|
||||
println!("=== Users List ===");
|
||||
let mut count = 0;
|
||||
for user in users {
|
||||
@@ -88,7 +90,7 @@ pub fn handle_user_command(cmd: UserCommand) -> anyhow::Result<()> {
|
||||
println!(" {} (role: {}, created: {})", name, role, created_at);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
|
||||
if count == 0 {
|
||||
println!("No users found");
|
||||
} else {
|
||||
@@ -97,24 +99,27 @@ pub fn handle_user_command(cmd: UserCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
UserCommand::Show { name } => {
|
||||
let db_path = "data/auth.sqlite";
|
||||
|
||||
|
||||
if !std::path::Path::new(db_path).exists() {
|
||||
return Err(anyhow::anyhow!("Auth database not found: {}", db_path));
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)
|
||||
.context("Failed to open auth database")?;
|
||||
|
||||
let user = conn.query_row(
|
||||
"SELECT username, role, created_at FROM sftpgo_users WHERE username = ?",
|
||||
[&name],
|
||||
|row| Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
))
|
||||
).context("Failed to query user")?;
|
||||
|
||||
|
||||
let conn = Connection::open(db_path).context("Failed to open auth database")?;
|
||||
|
||||
let user = conn
|
||||
.query_row(
|
||||
"SELECT username, role, created_at FROM sftpgo_users WHERE username = ?",
|
||||
[&name],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
))
|
||||
},
|
||||
)
|
||||
.context("Failed to query user")?;
|
||||
|
||||
let (username, role, created_at) = user;
|
||||
println!("=== User Details ===");
|
||||
println!("Username: {}", username);
|
||||
@@ -123,31 +128,30 @@ pub fn handle_user_command(cmd: UserCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
UserCommand::Delete { name } => {
|
||||
let db_path = "data/auth.sqlite";
|
||||
|
||||
|
||||
if !std::path::Path::new(db_path).exists() {
|
||||
return Err(anyhow::anyhow!("Auth database not found: {}", db_path));
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)
|
||||
.context("Failed to open auth database")?;
|
||||
|
||||
let exists: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM sftpgo_users WHERE username = ?",
|
||||
[&name],
|
||||
|row| row.get(0)
|
||||
).context("Failed to check user existence")?;
|
||||
|
||||
|
||||
let conn = Connection::open(db_path).context("Failed to open auth database")?;
|
||||
|
||||
let exists: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM sftpgo_users WHERE username = ?",
|
||||
[&name],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.context("Failed to check user existence")?;
|
||||
|
||||
if exists == 0 {
|
||||
return Err(anyhow::anyhow!("User not found: {}", name));
|
||||
}
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM sftpgo_users WHERE username = ?",
|
||||
[&name]
|
||||
).context("Failed to delete user")?;
|
||||
|
||||
|
||||
conn.execute("DELETE FROM sftpgo_users WHERE username = ?", [&name])
|
||||
.context("Failed to delete user")?;
|
||||
|
||||
println!("✓ User deleted: {}", name);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,4 +22,4 @@ pub enum Commands {
|
||||
Storage(storage::StorageCommands),
|
||||
#[command(flatten)]
|
||||
Tools(tools::ToolsCommands),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,56 +21,56 @@ pub fn handle_archive_command(cmd: ArchiveCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
ArchiveCommand::Decompress { file, output } => {
|
||||
use crate::archive::{ArchiveConfig, ProcessorRegistry};
|
||||
|
||||
|
||||
println!("Decompressing {} to {}", file, output);
|
||||
|
||||
|
||||
let archive_path = Path::new(&file);
|
||||
if !archive_path.exists() {
|
||||
return Err(anyhow::anyhow!("Archive file not found: {}", file));
|
||||
}
|
||||
|
||||
|
||||
let config = ArchiveConfig::default();
|
||||
let mut registry = ProcessorRegistry::new(config);
|
||||
registry.initialize()?;
|
||||
|
||||
|
||||
let output_path = Path::new(&output);
|
||||
std::fs::create_dir_all(output_path)?;
|
||||
|
||||
|
||||
let processor = registry.get_processor_mut(archive_path)?;
|
||||
let result = processor.extract_all(output_path)?;
|
||||
|
||||
|
||||
println!("✓ Archive decompressed to: {}", output);
|
||||
println!("✓ Files extracted: {}", result.success_files);
|
||||
println!("✓ Total size: {} bytes", result.total_bytes);
|
||||
}
|
||||
ArchiveCommand::List { file } => {
|
||||
use crate::archive::{ArchiveConfig, ProcessorRegistry};
|
||||
|
||||
|
||||
println!("Listing contents of {}", file);
|
||||
|
||||
|
||||
let archive_path = Path::new(&file);
|
||||
if !archive_path.exists() {
|
||||
return Err(anyhow::anyhow!("Archive file not found: {}", file));
|
||||
}
|
||||
|
||||
|
||||
let config = ArchiveConfig::default();
|
||||
let mut registry = ProcessorRegistry::new(config);
|
||||
registry.initialize()?;
|
||||
|
||||
|
||||
let processor = registry.get_processor_mut(archive_path)?;
|
||||
let metadata = processor.open(archive_path)?;
|
||||
let entries = processor.list_entries()?;
|
||||
|
||||
|
||||
println!("=== Archive Contents ===");
|
||||
println!("Format: {}", metadata.format);
|
||||
println!("Total files: {}", metadata.total_files);
|
||||
println!("Total size: {} bytes", metadata.total_size);
|
||||
println!("");
|
||||
|
||||
println!();
|
||||
|
||||
for entry in entries {
|
||||
println!(" {} ({} bytes)", entry.path.display(), entry.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,4 @@ pub fn handle_hash_command(cmd: HashCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
pub mod scan;
|
||||
pub mod hash;
|
||||
pub mod archive;
|
||||
pub mod sync;
|
||||
pub mod hash;
|
||||
pub mod mount;
|
||||
pub mod scan;
|
||||
pub mod sync;
|
||||
|
||||
use clap::Subcommand;
|
||||
|
||||
@@ -29,4 +29,4 @@ pub async fn handle_storage_command(cmd: StorageCommands) -> anyhow::Result<()>
|
||||
StorageCommands::Mount(c) => mount::handle_mount_command(c)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,21 +22,25 @@ pub enum MountCommand {
|
||||
|
||||
pub fn handle_mount_command(cmd: MountCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
MountCommand::Attach { type_, server, path } => {
|
||||
MountCommand::Attach {
|
||||
type_,
|
||||
server,
|
||||
path,
|
||||
} => {
|
||||
use std::process::Command;
|
||||
|
||||
|
||||
println!("Mounting {} from {} to {}", type_, server, path);
|
||||
|
||||
|
||||
if type_ == "nfs" {
|
||||
let mount_point = std::path::Path::new(&path);
|
||||
std::fs::create_dir_all(mount_point)?;
|
||||
|
||||
|
||||
let nfs_path = format!("{}:{}", server, path);
|
||||
|
||||
|
||||
let status = Command::new("mount")
|
||||
.args(["-t", "nfs", &nfs_path, &path])
|
||||
.status()?;
|
||||
|
||||
|
||||
if status.success() {
|
||||
println!("✓ NFS mounted: {} to {}", nfs_path, path);
|
||||
} else {
|
||||
@@ -45,31 +49,32 @@ pub fn handle_mount_command(cmd: MountCommand) -> anyhow::Result<()> {
|
||||
} else if type_ == "smb" {
|
||||
let mount_point = std::path::Path::new(&path);
|
||||
std::fs::create_dir_all(mount_point)?;
|
||||
|
||||
|
||||
let smb_path = format!("//{}", server);
|
||||
|
||||
|
||||
let status = Command::new("mount")
|
||||
.args(["-t", "smbfs", &smb_path, &path])
|
||||
.status()?;
|
||||
|
||||
|
||||
if status.success() {
|
||||
println!("✓ SMB mounted: {} to {}", smb_path, path);
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("SMB mount failed"));
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Unknown mount type: {}. Use 'nfs' or 'smb'", type_));
|
||||
return Err(anyhow::anyhow!(
|
||||
"Unknown mount type: {}. Use 'nfs' or 'smb'",
|
||||
type_
|
||||
));
|
||||
}
|
||||
}
|
||||
MountCommand::Detach { path } => {
|
||||
use std::process::Command;
|
||||
|
||||
|
||||
println!("Unmounting {}", path);
|
||||
|
||||
let status = Command::new("umount")
|
||||
.arg(&path)
|
||||
.status()?;
|
||||
|
||||
|
||||
let status = Command::new("umount").arg(&path).status()?;
|
||||
|
||||
if status.success() {
|
||||
println!("✓ Unmounted: {}", path);
|
||||
} else {
|
||||
@@ -78,14 +83,13 @@ pub fn handle_mount_command(cmd: MountCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
MountCommand::List => {
|
||||
use std::process::Command;
|
||||
|
||||
|
||||
println!("Listing mounted storage");
|
||||
|
||||
let output = Command::new("mount")
|
||||
.output()?;
|
||||
|
||||
|
||||
let output = Command::new("mount").output()?;
|
||||
|
||||
let mounts = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
|
||||
println!("=== Mounted Filesystems ===");
|
||||
for line in mounts.lines() {
|
||||
if line.contains("nfs") || line.contains("smbfs") || line.contains("fuse") {
|
||||
@@ -95,4 +99,4 @@ pub fn handle_mount_command(cmd: MountCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +31,4 @@ pub fn handle_scan_command(cmd: ScanCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,27 +17,31 @@ pub enum SyncCommand {
|
||||
|
||||
pub fn handle_sync_command(cmd: SyncCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
SyncCommand::Start { source, target, mode } => {
|
||||
SyncCommand::Start {
|
||||
source,
|
||||
target,
|
||||
mode,
|
||||
} => {
|
||||
use std::path::Path;
|
||||
|
||||
|
||||
println!("Syncing {} to {} (mode: {})", source, target, mode);
|
||||
|
||||
|
||||
let source_path = Path::new(&source);
|
||||
let target_path = Path::new(&target);
|
||||
|
||||
|
||||
if !source_path.exists() {
|
||||
return Err(anyhow::anyhow!("Source path not found: {}", source));
|
||||
}
|
||||
|
||||
|
||||
if mode == "mirror" {
|
||||
std::fs::create_dir_all(target_path)?;
|
||||
|
||||
|
||||
let entries = std::fs::read_dir(source_path)?;
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let target_file = target_path.join(entry.file_name());
|
||||
|
||||
|
||||
if path.is_file() {
|
||||
std::fs::copy(&path, &target_file)?;
|
||||
println!(" Copied: {:?}", entry.file_name());
|
||||
@@ -46,7 +50,7 @@ pub fn handle_sync_command(cmd: SyncCommand) -> anyhow::Result<()> {
|
||||
println!(" Created directory: {:?}", entry.file_name());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
println!("✓ Sync completed (mirror mode)");
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Unknown sync mode: {}. Use 'mirror'", mode));
|
||||
@@ -59,4 +63,4 @@ pub fn handle_sync_command(cmd: SyncCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,4 @@ pub async fn handle_tools_command(cmd: ToolsCommands) -> anyhow::Result<()> {
|
||||
ToolsCommands::Test(c) => test::handle_test_command(c)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,4 @@ pub fn handle_render_command(cmd: RenderCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,36 +16,39 @@ pub enum TestCommand {
|
||||
|
||||
pub fn handle_test_command(cmd: TestCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
TestCommand::Bcrypt { password, verify_hash } => {
|
||||
TestCommand::Bcrypt {
|
||||
password,
|
||||
verify_hash,
|
||||
} => {
|
||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||
|
||||
println!("=== bcrypt Hash Test ===");
|
||||
println!("Password: {}", password);
|
||||
println!("");
|
||||
println!();
|
||||
|
||||
let new_hash = hash(&password, DEFAULT_COST)?;
|
||||
println!("Generated hash:");
|
||||
println!("{}", new_hash);
|
||||
println!("");
|
||||
println!();
|
||||
|
||||
if let Some(hash_to_verify) = verify_hash {
|
||||
println!("Verifying hash: {}", hash_to_verify);
|
||||
let valid = verify(&password, &hash_to_verify)?;
|
||||
println!("Valid: {}", valid);
|
||||
println!("");
|
||||
println!();
|
||||
}
|
||||
|
||||
let db_hash = "$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6";
|
||||
println!("Database hash: {}", db_hash);
|
||||
let valid = verify(&password, db_hash)?;
|
||||
println!("Database hash valid for '{}': {}", password, valid);
|
||||
println!("");
|
||||
println!();
|
||||
|
||||
if !valid {
|
||||
println!("❌ Database hash is incorrect!");
|
||||
println!("Update SQL:");
|
||||
println!("UPDATE sftpgo_users SET password_hash = '{}' WHERE username IN ('testuser', 'demo', 'warren', 'momentry');", new_hash);
|
||||
println!("");
|
||||
println!();
|
||||
println!("Execute:");
|
||||
println!("sqlite3 data/auth.sqlite \"UPDATE sftpgo_users SET password_hash = '{}' WHERE username IN ('testuser', 'demo', 'warren', 'momentry');\"", new_hash);
|
||||
} else {
|
||||
@@ -58,4 +61,4 @@ pub fn handle_test_command(cmd: TestCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user