xmpd/src/downloader.rs

185 lines
6.6 KiB
Rust

use std::{collections::HashMap, path::PathBuf, process::Stdio};
use lazy_static::lazy_static;
use log::Level;
use tokio::sync::{Mutex, RwLock};
use crate::{config::ConfigWrapper, manifest::{song::{Song, SongType}, Format, Manifest}};
#[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()));
);
#[derive(Debug, Default)]
pub struct Downloader {
count: usize,
nb_initial_song_count: usize,
nb_cache: Vec<(String, String, Song, Format)>
}
impl Downloader {
pub fn new() -> Self {
Self {
..Default::default()
}
}
pub fn get_initial_song_count_nb(&self) -> usize {
self.nb_initial_song_count
}
pub fn get_songs_left_nb(&self) -> usize {
self.nb_cache.len() + crate::process_manager::proc_count()
}
pub fn download_song_nb(&mut self, cfg: &ConfigWrapper, pname: &String, sname: &String, song: &Song, format: &Format) -> anyhow::Result<()> {
self.nb_cache.push((pname.clone(), sname.clone(), song.clone(), format.clone()));
self.nb_initial_song_count += 1;
self.download_all_nb_poll(cfg)?;
Ok(())
}
pub fn download_all_nb(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result<Option<usize>> {
for (pname, playlist) in manifest.get_playlists() {
for (sname, song) in playlist.get_songs() {
self.nb_cache.push((pname.clone(), sname.clone(), song.clone(), manifest.get_format().clone()));
}
}
self.nb_initial_song_count = self.nb_cache.len();
self.download_all_nb_poll(cfg)
}
pub fn download_all_nb_poll(&mut self, cfg: &ConfigWrapper) -> anyhow::Result<Option<usize>> {
if !crate::process_manager::is_proc_queue_full(10) {
if let Some((pname, sname, song, format)) = self.nb_cache.pop() {
self.download_song(cfg, &sname, &song, &pname, &format)?;
}
}
if self.get_songs_left_nb() == 0 {
self.nb_initial_song_count = 0;
}
if crate::process_manager::proc_count() == 0 && self.nb_cache.is_empty() {
Ok(None)
} else {
Ok(Some(crate::process_manager::purge_done_procs()))
}
}
pub async fn download_all(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result<usize> {
let format = manifest.get_format();
for (name, playlist) in manifest.get_playlists() {
for (song_name, song) in playlist.get_songs() {
self.download_song(cfg, song_name, song, &name, format)?;
self.count += crate::process_manager::wait_for_procs_untill(10)?;
}
}
self.count += crate::process_manager::wait_for_procs_untill(0)?;
Ok(self.count)
}
#[allow(dead_code)]
pub fn download_playlist(&mut self, cfg: &ConfigWrapper, url: &String, pname: &String, format: &Format) -> anyhow::Result<usize> {
self.download_playlist_nb(cfg, url, pname, format)?;
let mut count = 0;
while let Some(c) = self.download_all_nb_poll(cfg)? {
count += c;
}
Ok(count)
}
pub fn download_playlist_nb(&mut self, cfg: &ConfigWrapper, url: &String, pname: &String, format: &Format) -> anyhow::Result<HashMap<String, Song>> {
log::warn!("This automatically assumes its a youtube link as it is currently the only supported playlist source");
let mut cmd = tokio::process::Command::new(&cfg.cfg.ytdlp.path);
cmd.args([
"--flat-playlist",
"--simulate",
"-O", "%(url)s|%(title)s",
url.as_str()
]);
cmd
.stderr(Stdio::null())
.stdout(Stdio::piped());
let ftr = cmd.output();
let mut ret = HashMap::new();
let out = futures::executor::block_on(ftr)?.stdout;
let out = String::from_utf8(out)?;
for line in out.lines() {
let mut split_text = line.split("|").collect::<Vec<&str>>();
let url = split_text.swap_remove(0).to_string();
let sname = split_text.join("|");
let song = Song::from_url_str(url)?.set_type(SongType::Youtube).clone();
self.nb_cache.push((pname.clone(), sname.clone(), song.clone(), format.clone()));
ret.insert(sname, song.clone());
}
self.nb_initial_song_count += out.lines().count();
self.download_all_nb_poll(cfg)?;
Ok(ret)
}
pub fn download_song(&mut self, cfg: &ConfigWrapper, name: &String, song: &Song, playlist: &String, format: &Format) -> anyhow::Result<()> {
let dl_dir = format!("{}/{playlist}", cfg.cli.output);
let dl_file = format!("{dl_dir}/{}.{}", name, &format);
log::debug!("Checking: {dl_file}");
if PathBuf::from(&dl_file).exists() {
log::debug!("File {dl_file} exists, skipping");
return Ok(())
}
log::debug!("File {dl_file} doesnt exist, downloading");
let mut cmd = match song.get_type() {
&SongType::Youtube => {
log::debug!("Song {} is from yotube", song.get_url_str());
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.get_url_str().as_str()
]);
cmd
}
SongType::Spotify => {
let mut cmd = tokio::process::Command::new(&cfg.cfg.spotdl.path);
cmd.args([
"--format",
&format.to_string(),
"--output",
dl_dir.as_str(),
song.get_url_str().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());
};
crate::process_manager::add_proc(cmd, format!("Downloaded {dl_file}"))?;
Ok(())
}
}