use clap::Parser;
use clap::CommandFactory;
use std::error::Error;
use std::sync::mpsc::{self, Receiver as MpscReceiver, Sender as MpscSender};
use std::thread;
use std::time::Duration;
use crate::download::download as cli_download;
use crate::advanced_download::AdvancedDownloader;
use crate::config::Config;
use crate::optimization::Optimizer;
use crate::gui::{KelpsGetGui, DownloadCommand, WorkerToGuiMessage};
pub use crate::ftp::FtpDownloader;
pub use crate::sftp::SftpDownloader;
pub use crate::torrent::TorrentDownloader;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(default_value_t = String::new())]
url: String,
#[arg(short = 'O', long = "output")]
output: Option<String>,
#[arg(short = 'q', long = "quiet")]
quiet: bool,
#[arg(short = 'a', long = "advanced")]
advanced: bool,
#[arg(short = 't', long = "torrent")]
torrent: bool,
#[arg(short = 'p', long = "proxy")]
proxy: Option<String>,
#[arg(long = "proxy-user")]
proxy_user: Option<String>,
#[arg(long = "proxy-pass")]
proxy_pass: Option<String>,
#[arg(long = "proxy-type", default_value = "http")]
proxy_type: String,
#[arg(short = 'l', long = "limit")]
speed_limit: Option<u64>,
#[arg(long = "no-cache")]
no_cache: bool,
#[arg(long = "max-peers")]
max_peers: Option<usize>,
#[arg(long = "max-seeds")]
max_seeds: Option<usize>,
#[arg(long = "torrent-port")]
torrent_port: Option<u16>,
#[arg(long = "no-dht")]
no_dht: bool,
#[arg(long = "gui")]
gui: bool,
#[arg(long = "ftp")]
ftp: bool,
#[arg(long = "sftp")]
sftp: bool,
}
mod download;
mod progress;
mod utils;
mod advanced_download;
mod config;
mod optimization;
mod ftp;
mod gui;
mod sftp;
mod torrent;
fn download_worker(
config: Config,
download_rx: MpscReceiver<DownloadCommand>,
status_tx: MpscSender<WorkerToGuiMessage>,
runtime: tokio::runtime::Runtime,
) {
for command in download_rx {
match command {
DownloadCommand::Start(url, output_path) => {
let _ = status_tx.send(WorkerToGuiMessage::StatusUpdate(format!(
"Preparing download: {} to {}",
url, output_path
)));
let optimizer_clone = Optimizer::new(config.optimization.clone());
let proxy_clone = config.proxy.clone();
let result: Result<(), Box<dyn Error + Send + Sync>> = if url.starts_with("magnet:?") {
let downloader = TorrentDownloader::new(
url.clone(),
output_path.clone(),
false,
proxy_clone,
optimizer_clone,
);
runtime.block_on(downloader.download())
} else if url.starts_with("ftp://") {
let downloader = FtpDownloader::new(
url.clone(),
output_path.clone(),
false,
proxy_clone,
optimizer_clone,
);
downloader.download().map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) as Box<dyn Error + Send + Sync>)
} else if url.starts_with("sftp://") {
let downloader = SftpDownloader::new(
url.clone(),
output_path.clone(),
false,
proxy_clone,
optimizer_clone,
);
downloader.download().map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) as Box<dyn Error + Send + Sync>)
} else {
status_tx.send(WorkerToGuiMessage::StatusUpdate(format!("Starting HTTP download: {}", url))).unwrap();
for i in 1..=10 {
thread::sleep(Duration::from_millis(200));
let _ = status_tx.send(WorkerToGuiMessage::Progress(i as f32 / 10.0));
}
Ok(())
};
match result {
Ok(_) => {
let _ = status_tx.send(WorkerToGuiMessage::Completed(format!(
"Successfully downloaded {} to {}",
url, output_path
)));
}
Err(e) => {
let _ = status_tx.send(WorkerToGuiMessage::Error(format!(
"Download failed for {}: {}",
url, e
)));
}
}
}
DownloadCommand::Cancel => {
let _ = status_tx.send(WorkerToGuiMessage::StatusUpdate(
"Cancel command received (implement cancellation logic in worker).".to_string(),
));
}
}
}
}
fn main() -> Result<(), Box<dyn Error>> {
let args = Args::parse();
let mut config = Config::load()?;
if !args.gui {
if let Some(proxy_url) = args.proxy.clone() {
config.proxy.enabled = true;
config.proxy.url = Some(proxy_url);
}
if let Some(user) = args.proxy_user.clone() {
config.proxy.username = Some(user);
}
if let Some(pass) = args.proxy_pass.clone() {
config.proxy.password = Some(pass);
}
config.proxy.proxy_type = match args.proxy_type.to_lowercase().as_str() {
"https" => crate::config::ProxyType::Https,
"socks5" => crate::config::ProxyType::Socks5,
_ => crate::config::ProxyType::Http,
};
if let Some(limit) = args.speed_limit {
config.optimization.speed_limit = Some(limit);
}
if args.no_cache {
config.optimization.cache_enabled = false;
}
if args.torrent {
config.torrent.enabled = true;
}
if let Some(max_peers) = args.max_peers {
config.torrent.max_peers = max_peers;
}
if let Some(max_seeds) = args.max_seeds {
config.torrent.max_seeds = max_seeds;
}
if let Some(port) = args.torrent_port {
config.torrent.port = Some(port);
}
if args.no_dht {
config.torrent.dht_enabled = false;
}
config.save()?;
}
if args.gui {
let (download_tx, download_rx_worker): (MpscSender<DownloadCommand>, MpscReceiver<DownloadCommand>) = mpsc::channel();
let (status_tx_worker, status_rx_gui): (MpscSender<WorkerToGuiMessage>, MpscReceiver<WorkerToGuiMessage>) = mpsc::channel();
let worker_config = config.clone();
let runtime = tokio::runtime::Runtime::new()?;
thread::spawn(move || {
download_worker(worker_config, download_rx_worker, status_tx_worker, runtime);
});
let native_options = eframe::NativeOptions::default();
eframe::run_native(
"KelpsGet Downloader",
native_options,
Box::new(move |cc| {
Ok(Box::new(KelpsGetGui::new(cc, download_tx, status_rx_gui)))
}),
)?;
return Ok(());
}
let optimizer = Optimizer::new(config.optimization.clone());
if args.url.is_empty() && !args.gui {
Args::command().print_help()?;
return Err("URL is required for CLI mode.".into());
}
if args.ftp {
let url = args.url.clone();
let output = args.output.unwrap_or_else(|| utils::get_filename_from_url_or_default(&url, "ftp_output"));
let downloader = FtpDownloader::new(
url,
output,
args.quiet,
config.proxy,
optimizer,
);
return downloader.download();
}
if args.sftp {
let url = args.url.clone();
let output = args.output.unwrap_or_else(|| utils::get_filename_from_url_or_default(&url, "sftp_output"));
let downloader = SftpDownloader::new(
url,
output,
args.quiet,
config.proxy,
optimizer,
);
return downloader.download();
}
if args.torrent || args.url.starts_with("magnet:?") {
let downloader = TorrentDownloader::new(
args.url,
args.output.unwrap_or_else(|| "torrent_output".to_string()),
args.quiet,
config.proxy,
optimizer,
);
tokio::runtime::Runtime::new()?
.block_on(downloader.download())
.map_err(|e| e as Box<dyn Error>)?;
} else if args.advanced {
let downloader = AdvancedDownloader::new(
args.url.clone(),
args.output.unwrap_or_else(|| utils::get_filename_from_url_or_default(&args.url, "advanced_output")),
args.quiet,
config.proxy,
optimizer,
);
downloader.download()?;
} else {
cli_download(
&args.url,
args.quiet,
args.output.or_else(|| Some(utils::get_filename_from_url_or_default(&args.url, "http_output"))),
config.proxy,
optimizer
).map_err(|e| e as Box<dyn Error>)?;
}
Ok(())
}