sproto 0.1.0

Rust client for the Synology Drive sync protocol
Documentation
#[path = "support/mod.rs"]
mod support;

use anyhow::{Context as _, Result, bail};
use clap::Parser;
use indicatif::{ProgressBar, ProgressStyle};
use std::path::PathBuf;

use sproto::actions::list::FileType;

#[derive(Parser)]
#[command(
    name = "sproto-dl",
    about = "Download a single file from Synology Drive"
)]
struct Args {
    /// Remote file path (e.g. /Documents/report.pdf)
    remote_path: String,

    /// Local destination path
    local_path: PathBuf,

    #[command(flatten)]
    conn: support::ConnectionArgs,
}

#[tokio::main]
async fn main() -> Result<()> {
    support::init_tracing();
    let args = Args::parse();

    let client = args.conn.connect().await?;
    let view_id = args.conn.resolve_view(&client).await?;

    let (parent, filename) = match args.remote_path.rsplit_once('/') {
        Some(("", name)) => ("/", name),
        Some((parent, name)) => (parent, name),
        None => bail!("remote path must be absolute (start with /)"),
    };

    let nodes = client
        .list_dir(view_id, parent)
        .await
        .with_context(|| format!("failed to list '{parent}'"))?;

    let file = nodes
        .iter()
        .find(|n| n.name == filename && n.file_type == FileType::File)
        .with_context(|| format!("file '{filename}' not found in '{parent}'"))?;

    let pb = ProgressBar::new(file.file_size);
    pb.set_style(
        #[allow(
            clippy::literal_string_with_formatting_args,
            reason = "indicatif template syntax"
        )]
        ProgressStyle::default_bar()
            .template("{msg} [{bar:40}] {bytes}/{total_bytes} ({bytes_per_sec})")
            .unwrap()
            .progress_chars("=> "),
    );
    pb.set_message(filename.to_string());

    let mut writer = support::ProgressWriter::new(args.local_path.clone(), pb.clone());
    client
        .download_to(&file.file_id, &mut writer)
        .await
        .context("download failed")?;

    pb.finish_with_message(format!("Saved to {}", args.local_path.display()));

    Ok(())
}