xmpd/music_mgr/src/downloader.rs
2024-04-19 00:19:11 +03:00

147 lines
4.6 KiB
Rust

use std::{collections::HashMap, path::PathBuf, process::Stdio, str::FromStr};
use lazy_static::lazy_static;
use log::Level;
use tokio::sync::{Mutex, RwLock};
use crate::{config::ConfigWrapper, manifest::{Manifest, ManifestSong}};
#[allow(dead_code)]
#[derive(Debug, Clone)]
struct Proc {
url: String,
path: String,
finished: bool
}
lazy_static!(
static ref PROCESSES: Mutex<RwLock<HashMap<usize, Proc>>> = Mutex::new(RwLock::new(HashMap::new()));
);
pub struct Downloader {
count: usize,
id_itr: usize,
}
impl Downloader {
pub fn new() -> Self {
Self {
count: 0,
id_itr: 0,
}
}
pub async fn download_all(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result<usize> {
let format = manifest.format()?;
for (genre, songs) in &manifest.genres {
for song in songs {
self.download_song(cfg, song, &genre, &format).await?;
self.wait_for_procs(10).await?;
}
}
self.wait_for_procs(0).await?;
Ok(self.count)
}
pub async fn download_song(&mut self, cfg: &ConfigWrapper, song: &ManifestSong, genre: &String, format: &String) -> anyhow::Result<()> {
let dl_dir = format!("{}/{genre}/", cfg.cli.output);
let dl_file = format!("{dl_dir}/{}.{}", song.name, &format);
if PathBuf::from(&dl_file).exists() {
log::debug!("File {dl_file} exists, skipping");
return Ok(())
}
let url = url::Url::from_str(&song.url)?;
let Some(url_host) = url.host() else {
log::error!("Url {} doesnt have a valid host name", &song.url);
return Ok(());
};
log::debug!("File {dl_file} doesnt exist, downloading");
let mut cmd = match url_host.to_string().as_str() {
"youtube.com" |
"youtu.be" => {
log::debug!("Song {} is from yotube", song.url);
let mut cmd = tokio::process::Command::new(&cfg.cfg.ytdlp.path);
cmd.args([
"-x",
"--audio-format",
&format.to_string(),
"-o",
dl_file.as_str(),
song.url.as_str()
]);
cmd
}
"open.spotify.com" => {
let mut cmd = tokio::process::Command::new(&cfg.cfg.spotdl.path);
cmd.args([
"--format",
&format.to_string(),
"--output",
dl_dir.as_str(),
song.url.as_str()
]);
cmd
}
url => {
log::error!("Unknown or unsupported hostname '{}'", url);
return Ok(());
}
};
if log::max_level() < Level::Debug {
cmd.stdout(Stdio::null()).stderr(Stdio::null());
};
let mut proc = cmd.spawn()?;
let id = self.id_itr;
tokio::spawn(async move {
let id = id;
proc.wait().await
.expect("child process encountered an error");
PROCESSES.lock().await.write().await.get_mut(&id).unwrap().finished = true;
});
log::info!("Downloading {dl_file}");
PROCESSES.lock().await.write().await.insert(id, Proc {
url: song.url.clone(),
path: dl_file,
finished: false,
});
self.id_itr += 1;
Ok(())
}
pub async fn wait_for_procs(&mut self, until: usize) -> anyhow::Result<()> {
// NOTE: This looks really fucked because i dont want to deadlock the processes so i lock PROCESSES for as little as possible
// NOTE: So its also kinda really slow
loop {
{
if PROCESSES.lock().await.read().await.len() <= until {
return Ok(());
}
}
let procs = {
PROCESSES.lock().await.read().await.clone()
};
for (idx, proc) in procs {
if proc.finished {
{
PROCESSES.lock().await.write().await.remove(&idx);
}
log::info!("Finished downloading {}", proc.path);
self.count += 1;
}
}
}
#[allow(unreachable_code)] //? rust_analizer not smart enough for this
Ok(())
}
}