769a5a68e2
Phase 5: Replace all Box<dyn Error> return types with anyhow::Result<T>
throughout the codebase. Replace string-based Err("msg".into()) and
format!().into() patterns with bail!() and anyhow!() macros. Fix
dirs::home_dir().unwrap() in settings.rs to use a fallback path instead
of panicking when HOME is unset. Remove stray use std::error::Error
imports no longer needed.
Phase 6: Add From<&User> for CacheUser in models/user.rs and
From<&Laboratory>/From<&Laboratories> for CacheLaboratory/CacheLabsWrapper
in models/laboratory.rs. Simplify commands/login.rs to use .into()
conversions, removing the redundant to_cache_user() and to_cache_labs()
helper functions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
221 lines
6.4 KiB
Rust
221 lines
6.4 KiB
Rust
mod api;
|
|
mod cache;
|
|
mod cli;
|
|
mod commands;
|
|
mod connection;
|
|
mod error;
|
|
mod models;
|
|
mod settings;
|
|
mod token;
|
|
|
|
use clap::Parser;
|
|
use cli::{Cli, Commands};
|
|
use error::handle_error;
|
|
|
|
fn run(cli: Cli) {
|
|
let build_rt = || {
|
|
tokio::runtime::Runtime::new()
|
|
.unwrap_or_else(|e| handle_error(e.into()))
|
|
};
|
|
|
|
match cli.command {
|
|
Commands::Config(subcmd) => {
|
|
use commands::config_subcommand::ConfigSubcommand;
|
|
match subcmd {
|
|
ConfigSubcommand::Create(args) => {
|
|
if let Err(e) = commands::config::config_create(&args.remote, &args.url) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
ConfigSubcommand::Update(args) => {
|
|
if let Err(e) = commands::config::config_update(&args.remote, &args.url) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
ConfigSubcommand::List(_) => {
|
|
if let Err(e) = commands::config::config_list() {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
ConfigSubcommand::Delete(args) => {
|
|
if let Err(e) = commands::config::config_delete(&args.remote) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Commands::Login {
|
|
username,
|
|
password,
|
|
remote,
|
|
} => {
|
|
let remote = remote.trim_end_matches(':').to_string();
|
|
if let Err(e) = build_rt().block_on(commands::login::run_login(
|
|
username.as_deref(),
|
|
password.as_deref(),
|
|
&remote,
|
|
)) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Logout { remote } => {
|
|
let remote = remote.trim_end_matches(':').to_string();
|
|
if let Err(e) = commands::logout::logout(&remote) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Upload {
|
|
local_path,
|
|
remote_path,
|
|
recursive,
|
|
skip_if_exists,
|
|
} => {
|
|
if let Err(e) = build_rt().block_on(commands::upload::upload(
|
|
&local_path,
|
|
&remote_path,
|
|
recursive,
|
|
skip_if_exists,
|
|
)) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Download {
|
|
remote_path,
|
|
local_path,
|
|
recursive,
|
|
skip_if_exists,
|
|
password,
|
|
exclude,
|
|
} => {
|
|
let excludes: Vec<String> = exclude
|
|
.iter()
|
|
.map(|e| e.trim_end_matches('/').to_lowercase())
|
|
.collect();
|
|
if let Err(e) = build_rt().block_on(commands::download::download(
|
|
&remote_path,
|
|
&local_path,
|
|
recursive,
|
|
skip_if_exists,
|
|
password.as_deref(),
|
|
excludes,
|
|
)) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Ls {
|
|
remote_path,
|
|
password,
|
|
json,
|
|
recursive,
|
|
quiet,
|
|
} => {
|
|
if let Err(e) = build_rt().block_on(commands::ls::ls(
|
|
&remote_path,
|
|
password.as_deref(),
|
|
json,
|
|
recursive,
|
|
quiet,
|
|
)) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Whoami { remote } => {
|
|
let remote = remote.trim_end_matches(':').to_string();
|
|
if let Err(e) = build_rt().block_on(commands::whoami::whoami(&remote)) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Labs { remote } => {
|
|
let remote = remote.trim_end_matches(':').to_string();
|
|
if let Err(e) = build_rt().block_on(commands::labs::labs(&remote)) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Chacl {
|
|
access_level_key,
|
|
recursive,
|
|
password,
|
|
remote_path,
|
|
} => {
|
|
if let Err(e) = build_rt().block_on(commands::chacl::chacl(
|
|
&remote_path,
|
|
&access_level_key,
|
|
recursive,
|
|
password.as_deref(),
|
|
)) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Metadata {
|
|
remote_path,
|
|
password,
|
|
} => {
|
|
if let Err(e) = build_rt()
|
|
.block_on(commands::metadata::metadata(&remote_path, password.as_deref()))
|
|
{
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Mkdir { remote_path } => {
|
|
if let Err(e) = build_rt().block_on(commands::mkdir::mkdir(&remote_path)) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Rm {
|
|
recursive,
|
|
remote_path,
|
|
} => {
|
|
if let Err(e) = build_rt().block_on(commands::rm::rm(&remote_path, recursive)) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Mv { src_path, dest_path } => {
|
|
if let Err(e) =
|
|
build_rt().block_on(commands::mv::mv(&src_path, &dest_path))
|
|
{
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Cp {
|
|
src_path,
|
|
dest_path,
|
|
recursive,
|
|
} => {
|
|
if let Err(e) =
|
|
build_rt().block_on(commands::cp::cp(&src_path, &dest_path, recursive))
|
|
{
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::FileMetadata {
|
|
remote_path,
|
|
password,
|
|
} => {
|
|
if let Err(e) = build_rt().block_on(commands::file_metadata::file_metadata(
|
|
&remote_path,
|
|
password.as_deref(),
|
|
)) {
|
|
handle_error(e);
|
|
}
|
|
}
|
|
Commands::Version => {
|
|
commands::version::version();
|
|
}
|
|
Commands::SelfUpdate { yes } => {
|
|
if let Err(e) = build_rt().block_on(commands::selfupdate::selfupdate(yes)) {
|
|
handle_error(e.into());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
// Load .env file from the current directory (silently ignore if not present).
|
|
dotenvy::dotenv().ok();
|
|
|
|
// Exit with code 130 on Ctrl+C, matching Python's KeyboardInterrupt handling.
|
|
ctrlc::set_handler(|| std::process::exit(130)).ok();
|
|
|
|
run(Cli::parse());
|
|
}
|