Files
mdrs-client-rust/src/cli.rs
T
Yoshihiro OKUMURA a16f73543d fix: stream transfers and close gaps against the Python client
A review against the Python client, which shares this client's config
and login cache, found the transfers holding whole files in memory and
several commands answering differently from their Python counterparts.

- Stream uploads and downloads instead of buffering the whole file, so
  memory no longer scales with file size times concurrency. Uploads
  still declare a Content-Length rather than going out chunked.
- Write a download beside its destination and move it into place once
  complete, and refuse a destination that cannot be written, so a
  failed transfer leaves what was already there untouched.
- Report the server's own error detail instead of the bare status.
- List locked sub-folders in `ls --json --recursive` using the given
  password and skip only those that cannot be unlocked, instead of
  failing the whole listing.
- Name each folder's laboratory in JSON output and sort its entries,
  matching the Python client's schema and order.
- Ask the server who is logged in for `whoami`, rather than trusting
  the cached name.
- Collapse repeated separators and refuse `..` in remote paths.
- Fall back to the API when a laboratory is missing from the cache, so
  a newly added one no longer needs a fresh login.
- Verify the login cache digest on read, as the Python client does.
- Accept `-e` as the short form of `--exclude`.
- Satisfy clippy and rustfmt across the crate.
2026-09-04 16:31:14 +09:00

115 lines
2.7 KiB
Rust

use clap::{Parser, Subcommand};
use crate::commands::config_subcommand::*;
#[derive(Parser)]
#[command(name = "mdrs")]
#[command(about = "MDRS Rust CLI client", long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
/// Config management (create, update, list, delete)
#[command(subcommand)]
Config(ConfigSubcommand),
Login {
#[arg(short, long)]
username: Option<String>,
#[arg(short, long)]
password: Option<String>,
remote: String,
},
/// Logout and remove cached credentials for a remote
Logout {
remote: String,
},
Upload {
#[arg(short, long)]
recursive: bool,
#[arg(short = 's', long)]
skip_if_exists: bool,
local_path: String,
remote_path: String,
},
Download {
#[arg(short, long)]
recursive: bool,
#[arg(short = 's', long)]
skip_if_exists: bool,
#[arg(short = 'p', long)]
password: Option<String>,
#[arg(short = 'e', long)]
exclude: Vec<String>,
remote_path: String,
local_path: String,
},
Ls {
remote_path: String,
#[arg(short = 'p', long)]
password: Option<String>,
#[arg(short = 'J', long = "json")]
json: bool,
#[arg(short = 'r', long)]
recursive: bool,
#[arg(short = 'q', long)]
quiet: bool,
},
Whoami {
remote: String,
},
Labs {
remote: String,
},
Chacl {
/// Access level key: private, public, pw_open, cbs_open, 5kikan_open,
/// cbs_or_pw_open, 5kikan_or_pw_open, storage
access_level_key: String,
#[arg(short, long)]
recursive: bool,
#[arg(short = 'p', long)]
password: Option<String>,
remote_path: String,
},
Metadata {
#[arg(short = 'p', long)]
password: Option<String>,
remote_path: String,
},
Mkdir {
remote_path: String,
},
Rm {
#[arg(short, long)]
recursive: bool,
remote_path: String,
},
Mv {
src_path: String,
dest_path: String,
},
Cp {
#[arg(short, long)]
recursive: bool,
src_path: String,
dest_path: String,
},
/// Show metadata for a remote file
FileMetadata {
#[arg(short = 'p', long)]
password: Option<String>,
remote_path: String,
},
/// Show the version of this tool
Version,
/// Update this binary to the latest release
#[command(name = "selfupdate")]
SelfUpdate {
/// Skip the confirmation prompt
#[arg(short = 'y', long)]
yes: bool,
},
}