Test Gitea Runner functionality

This commit is contained in:
Warren
2026-05-30 14:08:55 +08:00
parent 596d8d5e27
commit b362e9b3f1
44 changed files with 1 additions and 0 deletions

274
markbase-core/src/main.rs Normal file
View File

@@ -0,0 +1,274 @@
use clap::{Parser, Subcommand};
use std::path::Path;
#[derive(Parser)]
#[command(name = "markbase", about = "Momentry Display Engine")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Start display server
Display {
#[arg(short, long, default_value = "11438")]
port: u16,
/// Optional initial markdown file
file: Option<String>,
},
/// Render markdown to HTML (stdout)
Render {
file: String,
#[arg(short, long)]
output: Option<String>,
},
/// Configuration management
Config {
#[command(subcommand)]
action: ConfigCommands,
},
/// Scan and import files from directory
Scan {
/// User ID
#[arg(short, long)]
user: String,
/// Directory to scan
#[arg(short, long)]
dir: String,
/// Batch size for database insertion
#[arg(short, long, default_value = "100")]
batch: usize,
/// Skip SHA256 hash calculation (faster import)
#[arg(short, long, default_value = "true")]
skip_hash: bool,
/// Number of threads for hash calculation (if skip_hash=false)
#[arg(short, long, default_value = "4")]
threads: usize,
},
/// Compute SHA256 hashes for imported files
Hash {
/// User ID
#[arg(short, long)]
user: String,
/// Number of threads for parallel hash calculation
#[arg(short, long, default_value = "4")]
threads: usize,
},
}
#[derive(Subcommand)]
enum ConfigCommands {
/// Initialize default configuration file
Init {
#[arg(short, long)]
force: bool,
},
/// Show current configuration
Show {
#[arg(short, long)]
section: Option<String>,
},
/// Edit configuration
Edit {
#[arg(short, long)]
key: String,
#[arg(short, long)]
value: String,
},
/// Validate configuration
Validate,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Display { port, file } => {
markbase::server::run(port, file).await?;
}
Commands::Render { file, output } => {
let md = std::fs::read_to_string(&file)?;
let html = markbase::render::md_to_html(&md);
if let Some(path) = &output {
std::fs::write(path, html)?;
} else {
println!("{html}");
}
}
Commands::Config { action } => {
handle_config_command(action)?;
}
Commands::Scan { user, dir, batch, skip_hash, threads } => {
use markbase::scan::ScanOptions;
let options = ScanOptions {
skip_hash,
threads,
};
markbase::scan::scan_directory(&user, &dir, batch, options)?;
}
Commands::Hash { user, threads } => {
markbase::scan::compute_hashes(&user, threads)?;
}
Commands::WebDAV { action } => {
handle_webdav_command(action)?;
}
}
Ok(())
}
fn handle_config_command(action: ConfigCommands) -> anyhow::Result<()> {
match action {
ConfigCommands::Init { force } => {
let config_path = Path::new("config/markbase.toml");
if config_path.exists() && !force {
println!("Configuration file already exists at config/markbase.toml");
println!("Use --force to overwrite");
return Ok(());
}
let config = markbase::config::MarkBaseConfig::default_config();
config.save(config_path)?;
println!("✓ Configuration file created: config/markbase.toml");
println!("Default values:");
println!(" Server port: {}", config.server.port);
println!(" PostgreSQL host: {}", config.postgresql.host);
println!(" Test users: {}", config.test.users.join(", "));
}
ConfigCommands::Show { section } => {
let config_path = Path::new("config/markbase.toml");
if !config_path.exists() {
println!("Configuration file not found. Run 'markbase config init' first.");
return Ok(());
}
let config = markbase::config::MarkBaseConfig::load(config_path)?;
if let Some(s) = section {
show_section(&config, &s);
} else {
println!("{}", toml::to_string_pretty(&config)?);
}
}
ConfigCommands::Edit { key, value } => {
let config_path = Path::new("config/markbase.toml");
if !config_path.exists() {
println!("Configuration file not found. Run 'markbase config init' first.");
return Ok(());
}
let mut config = markbase::config::MarkBaseConfig::load(config_path)?;
match config.get(&key) {
Some(old_value) => {
config.set(&key, &value)?;
config.validate()?;
config.save(config_path)?;
println!("✓ Updated {}: {}{}", key, old_value, value);
}
None => {
println!("Invalid config key: {}", key);
println!("Valid keys: server.*, postgresql.*, authentication.*, test.*, logging.*");
}
}
}
ConfigCommands::Validate => {
let config_path = Path::new("config/markbase.toml");
if !config_path.exists() {
println!("Configuration file not found. Run 'markbase config init' first.");
return Ok(());
}
let config = markbase::config::MarkBaseConfig::load(config_path)?;
match config.validate() {
Ok(_) => {
println!("✓ Configuration is valid");
}
Err(e) => {
println!("✗ Configuration validation failed: {}", e);
}
}
}
}
Ok(())
}
fn show_section(config: &markbase::config::MarkBaseConfig, section: &str) {
match section {
"server" => println!("{}", toml::to_string_pretty(&config.server).unwrap()),
"postgresql" => println!("{}", toml::to_string_pretty(&config.postgresql).unwrap()),
"authentication" => println!("{}", toml::to_string_pretty(&config.authentication).unwrap()),
"test" => println!("{}", toml::to_string_pretty(&config.test).unwrap()),
"logging" => println!("{}", toml::to_string_pretty(&config.logging).unwrap()),
_ => println!("Invalid section: {}. Valid sections: server, postgresql, authentication, test, logging", section),
}
}
fn handle_webdav_command(action: WebDAVCommands) -> anyhow::Result<()> {
match action {
WebDAVCommands::Start { port, user } => {
use std::path::PathBuf;
use std::sync::Arc;
use markbase::webdav::MarkBaseWebDAV;
use markbase::filetree::FileTree;
use tokio::net::TcpListener;
let db_path = PathBuf::from(FileTree::user_db_path(&user));
if !db_path.exists() {
return Err(anyhow::anyhow!("User database not found: {}", db_path.display()));
}
println!("=== MarkBase WebDAV Server ===");
println!("User: {}", user);
println!("Port: {}", port);
println!("Database: {}", db_path.display());
println!("");
let webdav = MarkBaseWebDAV::new(user.clone(), db_path);
let dav_handler = webdav.create_handler();
let addr = format!("127.0.0.1:{}", port);
println!("Listening on: {}", addr);
println!("Mount with Finder:");
println!(" Connect to Server → http://localhost:{}/webdav", port);
println!("");
println!("Press Ctrl+C to stop...");
tokio::spawn(async move {
use axum::{Router, Extension, routing::any};
let app = Router::new()
.route("/webdav/*path", any(|req: axum::http::Request<axum::body::Body>, Extension(h): Extension<Arc<dav_server::DavHandler>>| async move {
use http_body_util::BodyExt;
let body = req.into_body().collect().await.unwrap().to_bytes();
let req = http::Request::new(body);
h.handle(req).await
}))
.route("/webdav", any(|req: axum::http::Request<axum::body::Body>, Extension(h): Extension<Arc<dav_server::DavHandler>>| async move {
use http_body_util::BodyExt;
let body = req.into_body().collect().await.unwrap().to_bytes();
let req = http::Request::new(body);
h.handle(req).await
}))
.layer(Extension(dav_handler));
let listener = TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
});
tokio::signal::ctrl_c().await?;
println!("\nShutting down...");
}
}
Ok(())
}