// yt - A fully featured command line YouTube client // // Copyright (C) 2024 Benedikt Peetz // SPDX-License-Identifier: GPL-3.0-or-later // // This file is part of Yt. // // You should have received a copy of the License along with this program. // If not, see . use std::{collections::HashMap, fs, sync::Arc}; use anyhow::{bail, Context, Result}; use app::App; use cache::invalidate; use clap::Parser; use cli::{CacheCommand, CheckCommand, SelectCommand, SubscriptionCommand}; use log::info; use select::cmds::handle_select_cmd; use tokio::{ fs::File, io::{stdin, BufReader}, }; use url::Url; use yt_dlp::wrapper::info_json::InfoJson; use crate::{cli::Command, storage::subscriptions::get_subscriptions}; pub mod app; pub mod cli; pub mod cache; pub mod comments; pub mod constants; pub mod download; pub mod select; pub mod status; pub mod storage; pub mod subscribe; pub mod update; pub mod watch; #[tokio::main] async fn main() -> Result<()> { let args = cli::CliArgs::parse(); stderrlog::new() .module(module_path!()) .modules(&["yt_dlp".to_owned(), "libmpv2".to_owned()]) .quiet(args.quiet) .show_module_names(false) .color(stderrlog::ColorChoice::Auto) .verbosity(args.verbosity as usize) .timestamp(stderrlog::Timestamp::Off) .init() .expect("Let's just hope that this does not panic"); let app = App::new(args.db_path.unwrap_or(constants::database()?)).await?; match args.command.unwrap_or(Command::default()) { Command::Download { force, max_cache_size, } => { info!("max cache size: '{}'", max_cache_size); if force { invalidate(&app, true).await?; } download::Downloader::new() .consume(Arc::new(app), max_cache_size) .await?; } Command::Select { cmd } => { let cmd = cmd.unwrap_or(SelectCommand::default()); match cmd { SelectCommand::File { done } => select::select(&app, done).await?, _ => handle_select_cmd(&app, cmd, None).await?, } } Command::Update { max_backlog, subscriptions, concurrent_processes, } => { let all_subs = get_subscriptions(&app).await?; for sub in &subscriptions { if let None = all_subs.0.get(sub) { bail!( "Your specified subscription to update '{}' is not a subscription!", sub ) } } update::update(&app, max_backlog, subscriptions, args.verbosity).await?; } Command::Subscriptions { cmd } => match cmd { SubscriptionCommand::Add { name, url } => { subscribe::subscribe(&app, name, url) .await .context("Failed to add a subscription")?; } SubscriptionCommand::Remove { name } => { subscribe::unsubscribe(&app, name) .await .context("Failed to remove a subscription")?; } SubscriptionCommand::List { url } => { let all_subs = get_subscriptions(&app).await?; if url { for val in all_subs.0.values() { println!("{}", val.url); } } else { for (key, val) in all_subs.0 { println!("{}: '{}'", key, val.url); } } } SubscriptionCommand::Import { file, force } => { if let Some(file) = file { let f = File::open(file).await?; subscribe::import(&app, BufReader::new(f), force).await? } else { subscribe::import(&app, BufReader::new(stdin()), force).await? }; } }, Command::Watch {} => watch::watch(&app).await?, Command::Status {} => status::show(&app).await?, Command::Database { command } => match command { CacheCommand::Invalidate { hard } => cache::invalidate(&app, hard).await?, CacheCommand::Maintain { all } => cache::maintain(&app, all).await?, }, Command::Check { command } => match command { CheckCommand::InfoJson { path } => { let string = fs::read_to_string(&path) .with_context(|| format!("Failed to read '{}' to string!", path.display()))?; let _: InfoJson = serde_json::from_str(&string).context("Failed to deserialize value")?; } CheckCommand::UpdateInfoJson { path } => { let string = fs::read_to_string(&path) .with_context(|| format!("Failed to read '{}' to string!", path.display()))?; let _: HashMap = serde_json::from_str(&string).context("Failed to deserialize value")?; } }, Command::Comments {} => { comments::comments(&app).await?; } Command::Description {} => { todo!() // description::description(&app).await?; } } Ok(()) }