use std::collections::HashMap;
use std::net::ToSocketAddrs;
use std::path::PathBuf;
use std::result::Result;
use std::sync::atomic::{AtomicBool, Ordering};
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::{generate, Shell};
use directories::{ProjectDirs, UserDirs};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use rews::configure::{
execute_configure_action, read_settings, ConfigurationError, ConfigureAction, Settings,
};
use rews::download::DownloadError;
use rews::queue::{Queue, QueueAction, QueueError};
use rews::PROJECT_DIR;
static EXIT: AtomicBool = AtomicBool::new(false);
fn main() -> Result<(), Error> {
let args = Cli::parse();
let project_dir =
ProjectDirs::from("no", "heksesang", "rews").ok_or(Error::MissingUserDirectory)?;
let user_dir = UserDirs::new().ok_or(Error::MissingUserDirectory)?;
let download_dir = user_dir.download_dir().ok_or(Error::MissingUserDirectory)?;
PROJECT_DIR.set(project_dir).unwrap();
std::fs::create_dir_all(PROJECT_DIR.wait().config_dir()).map_err(Error::DirectoryCreateFailed)?;
std::fs::create_dir_all(PROJECT_DIR.wait().data_dir()).map_err(Error::DirectoryCreateFailed)?;
std::fs::create_dir_all(download_dir).map_err(Error::DirectoryCreateFailed)?;
match args.command {
Commands::Queue { action } => action.execute().map_err(Error::QueueError),
Commands::Download => Queue::lock(|queue| {
let settings: Settings = read_settings(PROJECT_DIR.wait());
let server = settings
.download_server
.and_then(|server| settings.servers.get(&server))
.ok_or(Error::ConfigurationError(ConfigurationError::MissingServer))?;
format!("{}:80", server.host)
.to_socket_addrs()
.expect("Unable to resolve hostname");
let m = MultiProgress::new();
let style = ProgressStyle::default_bar()
.template(
"{prefix}\n{total_bytes:7.cyan} [{elapsed_precise}] {percent:>3}% {bar:40.cyan/blue} [{eta_precise}] ({bytes_per_sec})\n{msg}",
)
.unwrap()
.progress_chars("##>-");
let mut active_bars: HashMap<PathBuf, ProgressBar> = HashMap::new();
let download = queue
.run(download_dir, server)
.map_err(|e| Error::DownloadFailed(vec![e]))?;
ctrlc::set_handler(move || {
EXIT.store(true, Ordering::Relaxed);
})
.map_err(Error::HandlerError)?;
while let Ok((output_dir, message_id)) = download.next() {
if let Some(pb) = active_bars.get(&output_dir) {
pb.inc(queue.size(&message_id));
} else {
let find_result = queue.items.iter().find_map(|item| {
if item.path == output_dir {
let pb = m.add(ProgressBar::new(item.total_bytes));
pb.set_style(style.clone());
let downloaded_bytes = item.total_bytes - item.remaining_bytes();
if downloaded_bytes > 0 {
pb.set_position(item.total_bytes - item.remaining_bytes());
}
pb.set_prefix(PathBuf::from(&item.path).display().to_string());
pb.inc(queue.size(&message_id));
Some(pb)
} else {
None
}
});
if let Some(pb) = find_result {
active_bars.insert(output_dir, pb);
}
}
queue.remove_message(&message_id);
if EXIT.load(Ordering::Relaxed) && !download.is_cancelled() {
download.cancel();
}
}
Ok(())
}),
Commands::Configure { action } => {
execute_configure_action(action).map_err(Error::ConfigurationError)
}
Commands::Completions { shell } => {
let cmd = &mut Cli::command();
generate(
shell,
cmd,
cmd.get_name().to_string(),
&mut std::io::stdout(),
);
Ok(())
}
}
}
#[derive(Debug, Parser)]
#[clap(author, version, about, long_about = None)]
struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand)]
enum Commands {
Download,
Queue {
#[clap(subcommand)]
action: QueueAction,
},
Configure {
#[clap(subcommand)]
action: ConfigureAction,
},
Completions { shell: Shell },
}
#[derive(Debug)]
enum Error {
DownloadFailed(Vec<DownloadError>),
ConfigurationError(ConfigurationError),
MissingUserDirectory,
DirectoryCreateFailed(std::io::Error),
QueueError(QueueError),
HandlerError(ctrlc::Error),
}
impl From<QueueError> for Error {
fn from(e: QueueError) -> Self {
Self::QueueError(e)
}
}