Downloading prototype works
This commit is contained in:
parent
b29caa58b4
commit
fda77f6981
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
|||
/target/
|
||||
/cache/
|
||||
settings.toml
|
||||
valgrind.log
|
||||
|
|
42
Cargo.lock
generated
42
Cargo.lock
generated
|
@ -1,6 +1,6 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph"
|
||||
|
@ -729,6 +729,9 @@ name = "camino"
|
|||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
|
@ -4376,8 +4379,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "xmpd-cli"
|
||||
name = "xmpd-cache"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"uuid",
|
||||
"xmpd-cliargs",
|
||||
"xmpd-manifest",
|
||||
"xmpd-settings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmpd-cliargs"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"clap",
|
||||
"dirs",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmpd-core"
|
||||
|
@ -4386,24 +4409,20 @@ dependencies = [
|
|||
"anyhow",
|
||||
"camino",
|
||||
"clap",
|
||||
"dirs",
|
||||
"env_logger",
|
||||
"log",
|
||||
"xmpd-cli",
|
||||
"xmpd-cliargs",
|
||||
"xmpd-gui",
|
||||
"xmpd-manifest",
|
||||
"xmpd-settings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmpd-dl"
|
||||
version = "2.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "xmpd-gui"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"eframe",
|
||||
"egui 0.27.2",
|
||||
"egui-aesthetix",
|
||||
|
@ -4412,6 +4431,8 @@ dependencies = [
|
|||
"log",
|
||||
"tokio",
|
||||
"uuid",
|
||||
"xmpd-cache",
|
||||
"xmpd-cliargs",
|
||||
"xmpd-manifest",
|
||||
"xmpd-settings",
|
||||
]
|
||||
|
@ -4432,12 +4453,17 @@ name = "xmpd-settings"
|
|||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"egui 0.27.2",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmpd-tooling"
|
||||
version = "2.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "3.15.2"
|
||||
|
|
|
@ -4,9 +4,10 @@ members=[
|
|||
"xmpd-core",
|
||||
"xmpd-manifest",
|
||||
"xmpd-gui",
|
||||
#"xmpd-cli",
|
||||
"xmpd-dl",
|
||||
"xmpd-cliargs",
|
||||
"xmpd-cache",
|
||||
"xmpd-settings",
|
||||
"xmpd-tooling",
|
||||
# "xmpd-tui"
|
||||
]
|
||||
|
||||
|
@ -24,7 +25,7 @@ authors=[
|
|||
anstyle = "1.0.6"
|
||||
anyhow = "1.0.81"
|
||||
bitflags = { version = "2.6.0", features = ["serde"] }
|
||||
camino = "1.1.6"
|
||||
camino = { version="1.1.6", features = ["serde1"] }
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
eframe = "0.27.2"
|
||||
egui = { version = "0.27.2", features = ["color-hex", "serde"] }
|
||||
|
|
1
assets/check.svg
Normal file
1
assets/check.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z"/></svg>
|
After Width: | Height: | Size: 179 B |
1
assets/cross.svg
Normal file
1
assets/cross.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M256-213.85 213.85-256l224-224-224-224L256-746.15l224 224 224-224L746.15-704l-224 224 224 224L704-213.85l-224-224-224 224Z"/></svg>
|
After Width: | Height: | Size: 247 B |
1
assets/download.svg
Normal file
1
assets/download.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg>
|
After Width: | Height: | Size: 279 B |
1
assets/warning.svg
Normal file
1
assets/warning.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="m40-120 440-760 440 760H40Zm138-80h604L480-720 178-200Zm302-40q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm-40-120h80v-200h-80v200Zm40-100Z"/></svg>
|
After Width: | Height: | Size: 315 B |
2356
manifest.json
2356
manifest.json
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "xmpd-dl"
|
||||
name = "xmpd-cache"
|
||||
edition = "2021"
|
||||
readme="README.md"
|
||||
authors.workspace = true
|
||||
|
@ -18,3 +18,11 @@ crate-type = ["rlib"]
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
xmpd-settings.path = "../xmpd-settings"
|
||||
xmpd-manifest.path = "../xmpd-manifest"
|
||||
xmpd-cliargs.path = "../xmpd-cliargs"
|
||||
anyhow.workspace = true
|
||||
camino.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
uuid.workspace = true
|
4
xmpd-cache/src/downloader/mod.rs
Normal file
4
xmpd-cache/src/downloader/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
pub mod song;
|
||||
pub mod icon;
|
||||
pub mod metadata;
|
127
xmpd-cache/src/downloader/song.rs
Normal file
127
xmpd-cache/src/downloader/song.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
use std::{collections::HashMap, ffi::OsStr, process::{Command, Stdio}, sync::{Arc, Mutex, MutexGuard}};
|
||||
use xmpd_manifest::song::{Song, SourceType};
|
||||
|
||||
|
||||
lazy_static::lazy_static!(
|
||||
static ref SONG_CACHE_DL: Arc<Mutex<SongCacheDl>> = Arc::default();
|
||||
);
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub enum SongStatus {
|
||||
Downloading,
|
||||
Converting,
|
||||
Done
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SongCacheDl {
|
||||
pub jobs: HashMap<uuid::Uuid, SongStatus>,
|
||||
pub current_jobs: usize,
|
||||
}
|
||||
|
||||
impl SongCacheDl {
|
||||
pub fn get() -> crate::Result<MutexGuard<'static, Self>> {
|
||||
match SONG_CACHE_DL.lock() {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => anyhow::bail!(format!("{e:?}"))
|
||||
}
|
||||
}
|
||||
pub fn is_job_list_full(&self) -> bool {
|
||||
self.current_jobs >= 5
|
||||
}
|
||||
|
||||
pub fn download(&mut self, sid: uuid::Uuid, song: Song) -> crate::Result<()> {
|
||||
self.current_jobs += 1;
|
||||
let song_format = xmpd_settings::Settings::get().unwrap().tooling.song_format.clone();
|
||||
let tooling = xmpd_settings::Settings::get()?.tooling.clone();
|
||||
let mut song_cache_d = xmpd_cliargs::CLIARGS.cache_path();
|
||||
song_cache_d.push("songs");
|
||||
match song.source_type() {
|
||||
SourceType::Youtube |
|
||||
SourceType::Soundcloud => {
|
||||
let mut song_p = song_cache_d.clone();
|
||||
song_p.push(sid.to_string());
|
||||
let song_p = song_p.with_extension(&song_format);
|
||||
|
||||
let mut dl_cmd = Command::new(&tooling.ytdlp_path);
|
||||
dl_cmd.arg(song.url().as_str());
|
||||
dl_cmd.args(["-x", "--audio-format", &song_format]);
|
||||
dl_cmd.arg("-o");
|
||||
dl_cmd.arg(&song_p);
|
||||
|
||||
if xmpd_cliargs::CLIARGS.debug {
|
||||
dl_cmd.stdout(Stdio::piped());
|
||||
dl_cmd.stderr(Stdio::piped());
|
||||
} else {
|
||||
dl_cmd.stdout(Stdio::null());
|
||||
dl_cmd.stderr(Stdio::null());
|
||||
}
|
||||
let dl_child = dl_cmd.spawn()?;
|
||||
self.jobs.insert(sid, SongStatus::Downloading);
|
||||
std::thread::spawn(move || {
|
||||
if let Ok(output) = dl_child.wait_with_output() {
|
||||
for line in String::from_utf8(output.stdout).unwrap().lines() {
|
||||
log::info!("CMD: {}", line);
|
||||
}
|
||||
for line in String::from_utf8(output.stderr).unwrap().lines() {
|
||||
log::error!("CMD: {}", line);
|
||||
}
|
||||
}
|
||||
let mut cache = SONG_CACHE_DL.lock().unwrap();
|
||||
cache.jobs.insert(sid, SongStatus::Done);
|
||||
cache.current_jobs -= 1;
|
||||
});
|
||||
}
|
||||
|
||||
SourceType::Spotify => {
|
||||
// Spotdl doesnt have webm as a format so its fucking annoying, oh well
|
||||
let mut song_p = song_cache_d.clone();
|
||||
song_p.push(sid.to_string());
|
||||
song_p.push("{output-ext}");
|
||||
let mut dl_cmd = Command::new(&tooling.spotdl_path);
|
||||
dl_cmd.arg(song.url().as_str());
|
||||
dl_cmd.arg("--ffmpeg");
|
||||
dl_cmd.arg(&tooling.ffmpeg_path);
|
||||
dl_cmd.args(["--format", &song_format, "--output"]);
|
||||
dl_cmd.arg(&song_p);
|
||||
let arg_str = dl_cmd.get_args();
|
||||
let arg_str: Vec<_> = arg_str.collect();
|
||||
let arg_str = arg_str.join(OsStr::new(" ")).to_string_lossy().to_string();
|
||||
log::debug!("spotify cli: {} {}", tooling.spotdl_path, arg_str);
|
||||
if xmpd_cliargs::CLIARGS.debug {
|
||||
dl_cmd.stdout(Stdio::piped());
|
||||
dl_cmd.stderr(Stdio::piped());
|
||||
} else {
|
||||
dl_cmd.stdout(Stdio::null());
|
||||
dl_cmd.stderr(Stdio::null());
|
||||
}
|
||||
let child = dl_cmd.spawn()?;
|
||||
self.jobs.insert(sid, SongStatus::Downloading);
|
||||
std::thread::spawn(move || {
|
||||
if let Ok(output) = child.wait_with_output() {
|
||||
for line in String::from_utf8(output.stdout).unwrap().lines() {
|
||||
log::info!("CMD: {}", line);
|
||||
}
|
||||
for line in String::from_utf8(output.stderr).unwrap().lines() {
|
||||
log::error!("CMD: {}", line);
|
||||
}
|
||||
}
|
||||
let mut from = song_p.clone();
|
||||
from.pop();
|
||||
from.push("{song_format}.{song_format}");
|
||||
|
||||
let mut to = song_p.clone();
|
||||
to.pop();
|
||||
to.set_extension(&song_format);
|
||||
std::fs::copy(&from, &to).unwrap();
|
||||
from.pop();
|
||||
std::fs::remove_dir_all(from).unwrap();
|
||||
let mut cache = SONG_CACHE_DL.lock().unwrap();
|
||||
cache.jobs.insert(sid, SongStatus::Done);
|
||||
cache.current_jobs -= 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
122
xmpd-cache/src/lib.rs
Normal file
122
xmpd-cache/src/lib.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
use std::{collections::HashMap, str::FromStr, sync::{Arc, Mutex, MutexGuard}, time::Duration};
|
||||
use downloader::song::SongStatus;
|
||||
use xmpd_manifest::song::Song;
|
||||
|
||||
pub mod downloader;
|
||||
|
||||
type Result<T> = anyhow::Result<T>;
|
||||
|
||||
lazy_static::lazy_static!(
|
||||
static ref CACHE: Arc<Mutex<Cache>> = Arc::new(Mutex::new(Cache::default()));
|
||||
);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Cache {
|
||||
cache_dir: camino::Utf8PathBuf,
|
||||
song_cache: HashMap<uuid::Uuid, DlStatus>,
|
||||
queue: Vec<(uuid::Uuid, Song)>
|
||||
// TODO: Add Icon, metadata cache
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DlStatus {
|
||||
Done(camino::Utf8PathBuf),
|
||||
Downloading,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn get() -> crate::Result<MutexGuard<'static, Self>> {
|
||||
match CACHE.lock() {
|
||||
Ok(l) => Ok(l),
|
||||
Err(e) => Err(anyhow::anyhow!(format!("{e:?}"))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> Result<()> {
|
||||
start_cache_mv_thread();
|
||||
self.cache_dir = xmpd_cliargs::CLIARGS.cache_path();
|
||||
|
||||
{ // Get cached songs
|
||||
let mut song_cache_dir = self.cache_dir.clone();
|
||||
song_cache_dir.push("songs");
|
||||
for file in song_cache_dir.read_dir_utf8()? {
|
||||
if let Ok(file) = file {
|
||||
if !file.file_type()?.is_file() {
|
||||
log::warn!("Non song file in: {}", file.path());
|
||||
continue;
|
||||
}
|
||||
let file_path = file.path();
|
||||
let file2 = file_path.with_extension("");
|
||||
if let Some(file_name) = file2.file_name() {
|
||||
let id = uuid::Uuid::from_str(file_name)?;
|
||||
log::debug!("Found song {id}");
|
||||
// TODO: Check if id is in manifest
|
||||
self.song_cache.insert(id, DlStatus::Done(file_path.to_path_buf()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
{ // Get cached icons
|
||||
}
|
||||
{ // Get Cached meta
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn download_to_cache(&mut self, sid: uuid::Uuid, song: Song) {
|
||||
self.queue.push((sid, song));
|
||||
self.song_cache.insert(sid, DlStatus::Downloading);
|
||||
}
|
||||
|
||||
pub fn download_to_cache_multiple(&mut self, mut songs: Vec<(uuid::Uuid, Song)>) {
|
||||
while let Some((sid, song)) = songs.pop() {
|
||||
self.download_to_cache(sid, song);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cached_song_status(&mut self, sid: &uuid::Uuid) -> Option<DlStatus> {
|
||||
Some(self.song_cache.get(sid)?.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn start_cache_mv_thread() {
|
||||
std::thread::spawn(|| {
|
||||
loop {
|
||||
{
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
let song_format = xmpd_settings::Settings::get().unwrap().tooling.song_format.clone();
|
||||
let mut done_jobs = Vec::new();
|
||||
let mut dlc = downloader::song::SongCacheDl::get().unwrap();
|
||||
for (sid, status) in &dlc.jobs {
|
||||
if *status == SongStatus::Done {
|
||||
let mut cache = CACHE.lock().unwrap();
|
||||
let mut song_p = xmpd_cliargs::CLIARGS.cache_path().clone();
|
||||
song_p.push("songs");
|
||||
song_p.push(sid.clone().to_string());
|
||||
let song_p = song_p.with_extension(&song_format);
|
||||
log::debug!("Found done: {:?}: {}", song_p, song_p.exists());
|
||||
if song_p.exists() {
|
||||
cache.song_cache.insert(sid.clone(), DlStatus::Done(song_p));
|
||||
done_jobs.push(sid.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
for sid in done_jobs {
|
||||
dlc.jobs.remove(&sid);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut cache = Cache::get().unwrap();
|
||||
let mut dlc = downloader::song::SongCacheDl::get().unwrap();
|
||||
if !dlc.is_job_list_full() {
|
||||
if let Some((sid, song)) = cache.queue.pop() {
|
||||
dlc.download(sid, song).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
pub fn test() {
|
||||
println!("Hello, world!");
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "xmpd-cli"
|
||||
name = "xmpd-cliargs"
|
||||
edition = "2021"
|
||||
readme="README.md"
|
||||
version.workspace = true
|
||||
|
@ -18,3 +18,7 @@ crate-type = ["rlib"]
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
camino.workspace = true
|
||||
clap.workspace = true
|
||||
dirs.workspace = true
|
||||
lazy_static.workspace = true
|
0
xmpd-cliargs/README.md
Normal file
0
xmpd-cliargs/README.md
Normal file
|
@ -1,5 +1,11 @@
|
|||
use std::{path::PathBuf, str::FromStr};
|
||||
use std::{path::PathBuf, str::FromStr, sync::Arc};
|
||||
|
||||
use camino::Utf8PathBuf;
|
||||
use clap::Parser;
|
||||
|
||||
lazy_static::lazy_static!(
|
||||
pub static ref CLIARGS: Arc<CliArgs> = Arc::new(CliArgs::parse());
|
||||
);
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
pub struct CliArgs {
|
||||
|
@ -24,8 +30,8 @@ impl CliArgs {
|
|||
pub fn settings_path(&self) -> PathBuf {
|
||||
self.settings.clone().into_std_path_buf()
|
||||
}
|
||||
pub fn cache_path(&self) -> PathBuf {
|
||||
self.cache.clone().into_std_path_buf()
|
||||
pub fn cache_path(&self) -> Utf8PathBuf {
|
||||
self.cache.clone()
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ name="xmpd"
|
|||
path="src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
xmpd-cli.path="../xmpd-cli"
|
||||
xmpd-cliargs.path="../xmpd-cliargs"
|
||||
xmpd-gui.path="../xmpd-gui"
|
||||
xmpd-manifest.path="../xmpd-manifest"
|
||||
xmpd-settings.path = "../xmpd-settings"
|
||||
|
@ -30,4 +30,3 @@ camino.workspace = true
|
|||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
dirs.workspace = true
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use log::LevelFilter;
|
||||
|
||||
use crate::cli::CliArgs;
|
||||
|
||||
use xmpd_cliargs::CliArgs;
|
||||
|
||||
|
||||
pub fn init(cliargs: &CliArgs) {
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use std::borrow::BorrowMut;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
mod cli;
|
||||
mod logger;
|
||||
|
||||
type Result<T> = anyhow::Result<T>;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cliargs = cli::CliArgs::parse();
|
||||
// NOTE: Parses on first load
|
||||
let cliargs = &xmpd_cliargs::CLIARGS;
|
||||
logger::init(&cliargs);
|
||||
log::debug!("Cli: {cliargs:?}");
|
||||
log::debug!("Initialising settings");
|
||||
xmpd_settings::Settings::get()?.load(Some(cliargs.settings_path()))?;
|
||||
xmpd_gui::start(cliargs.manifest_path())?;
|
||||
log::debug!("Starting gui");
|
||||
xmpd_gui::start()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ bench = false
|
|||
[dependencies]
|
||||
xmpd-manifest.path = "../xmpd-manifest"
|
||||
xmpd-settings.path = "../xmpd-settings"
|
||||
xmpd-cliargs.path = "../xmpd-cliargs"
|
||||
xmpd-cache.path = "../xmpd-cache"
|
||||
egui.workspace = true
|
||||
eframe.workspace = true
|
||||
tokio.workspace = true
|
||||
|
@ -29,3 +31,4 @@ log.workspace = true
|
|||
egui_extras.workspace = true
|
||||
egui-aesthetix = "0.2.4"
|
||||
uuid.workspace = true
|
||||
camino.workspace = true
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use egui::{RichText, Vec2};
|
||||
use std::fmt::write;
|
||||
|
||||
use egui::{Color32, RichText, Sense, Vec2};
|
||||
use song_list_nav::SearchType;
|
||||
use xmpd_cache::DlStatus;
|
||||
use xmpd_manifest::{song::Song, store::BaseStore};
|
||||
use super::{CompGetter, CompUi};
|
||||
|
||||
|
@ -80,34 +83,92 @@ impl CompUi for SongList {
|
|||
|
||||
fn display_song_tab(ui: &mut egui::Ui, sid: &uuid::Uuid, song: &Song) {
|
||||
let theme = handle_error_ui!(xmpd_settings::Settings::get()).theme.clone();
|
||||
let rct = ui.horizontal(|ui| {
|
||||
ui.add(
|
||||
ui.horizontal(|ui| {
|
||||
let mut clicked = ui.add(
|
||||
egui::Image::new(crate::data::NOTE_ICON)
|
||||
.tint(theme.accent_color)
|
||||
.sense(Sense::click())
|
||||
.fit_to_exact_size(Vec2::new(32.0, 32.0))
|
||||
);
|
||||
).clicked();
|
||||
|
||||
ui.vertical(|ui| {
|
||||
let selected_song_id = {handle_error_ui!(SongList::get()).selected_song_id};
|
||||
if selected_song_id == *sid {
|
||||
let label = if selected_song_id == *sid {
|
||||
ui.label(
|
||||
RichText::new(song.name())
|
||||
.color(theme.accent_color)
|
||||
);
|
||||
)
|
||||
} else {
|
||||
ui.label(
|
||||
RichText::new(song.name())
|
||||
.color(theme.text_color)
|
||||
);
|
||||
)
|
||||
};
|
||||
|
||||
if label.clicked() {
|
||||
clicked = true;
|
||||
}
|
||||
ui.monospace(
|
||||
RichText::new(format!("By {}", song.author()))
|
||||
.color(theme.dim_text_color)
|
||||
.size(10.0)
|
||||
);
|
||||
|
||||
});
|
||||
}).response.rect;
|
||||
if ui.interact(rct, format!("song_list_{sid:?}").into(), egui::Sense::click()).clicked() {
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
|
||||
ui.add_space(3.0);
|
||||
let status = {
|
||||
handle_error_ui!(xmpd_cache::Cache::get()).get_cached_song_status(&sid).clone()
|
||||
};
|
||||
match status {
|
||||
Some(DlStatus::Done(p)) => {
|
||||
let img = ui.add(
|
||||
egui::Image::new(crate::data::CHECK_ICON)
|
||||
.tint(Color32::LIGHT_GREEN)
|
||||
.sense(Sense::hover())
|
||||
.fit_to_exact_size(Vec2::new(16.0, 16.0))
|
||||
);
|
||||
|
||||
img.on_hover_ui(|ui| {
|
||||
ui.label(format!("Path: {p}"));
|
||||
});
|
||||
}
|
||||
Some(DlStatus::Downloading) => {
|
||||
ui.add(
|
||||
egui::Spinner::new()
|
||||
.color(theme.accent_color)
|
||||
.size(16.0)
|
||||
);
|
||||
}
|
||||
Some(DlStatus::Error(e)) => {
|
||||
let img = ui.add(
|
||||
egui::Image::new(crate::data::WARN_ICON)
|
||||
.tint(Color32::LIGHT_YELLOW)
|
||||
.sense(Sense::hover())
|
||||
.fit_to_exact_size(Vec2::new(16.0, 16.0))
|
||||
);
|
||||
img.on_hover_ui(|ui| {
|
||||
ui.label(e);
|
||||
});
|
||||
}
|
||||
None => {
|
||||
let img = ui.add(
|
||||
egui::Image::new(crate::data::DL_ICON)
|
||||
.tint(theme.accent_color)
|
||||
.sense(Sense::click())
|
||||
.fit_to_exact_size(Vec2::new(16.0, 16.0))
|
||||
);
|
||||
if img.clicked() {
|
||||
handle_error_ui!(xmpd_cache::Cache::get()).download_to_cache(sid.clone(), song.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if clicked {
|
||||
handle_error_ui!(SongList::get()).selected_song_id = sid.clone();
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use crate::components::{CompGetter, CompUi};
|
||||
use uuid::Uuid;
|
||||
use xmpd_manifest::store::BaseStore;
|
||||
|
||||
use crate::components::{left_nav::LeftNav, CompGetter, CompUi};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SearchType {
|
||||
|
@ -9,14 +12,16 @@ pub enum SearchType {
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SongListNav {
|
||||
text: String
|
||||
text: String,
|
||||
}
|
||||
|
||||
component_register!(SongListNav);
|
||||
|
||||
impl CompUi for SongListNav {
|
||||
fn draw(ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
|
||||
fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> {
|
||||
let theme = xmpd_settings::Settings::get()?.theme.clone();
|
||||
let pid = {LeftNav::get()?.selected_playlist_id.clone()};
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let search_icon = egui::Image::new(crate::data::SEARCH_ICON)
|
||||
.fit_to_exact_size(egui::Vec2::new(16.0, 16.0))
|
||||
|
@ -25,6 +30,31 @@ impl CompUi for SongListNav {
|
|||
{
|
||||
ui.text_edit_singleline(&mut handle_error_ui!(SongListNav::get()).text);
|
||||
}
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
|
||||
let img = ui.add(
|
||||
egui::Image::new(crate::data::DL_ICON)
|
||||
.tint(theme.accent_color)
|
||||
.sense(egui::Sense::click())
|
||||
.fit_to_exact_size(egui::Vec2::new(16.0, 16.0))
|
||||
);
|
||||
if img.clicked() {
|
||||
let songs: Vec<_>;
|
||||
match pid {
|
||||
Some(pid) => {
|
||||
songs = state.manifest.store().get_playlist(&pid).unwrap().songs().to_vec();
|
||||
}
|
||||
None => {
|
||||
songs = state.manifest.store().get_songs().keys().cloned().collect();
|
||||
}
|
||||
}
|
||||
for sid in &songs {
|
||||
if let Some(song) = state.manifest.store().get_song(&sid) {
|
||||
handle_error_ui!(xmpd_cache::Cache::get()).download_to_cache(sid.clone(), song.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -14,16 +14,19 @@ impl CompUi for TopNav {
|
|||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Settings").clicked() {
|
||||
state.windows.toggle(&WindowId::Settings, true);
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
ui.menu_button("Manifest", |ui| {
|
||||
if ui.button("Save").clicked() {
|
||||
handle_error_ui!(state.manifest.save());
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
ui.menu_button("Help", |ui| {
|
||||
if ui.button("Source").clicked() {
|
||||
ui.ctx().open_url(egui::OpenUrl::new_tab("https://git.mcorangehq.xyz/XOR64/music"));
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -6,3 +6,6 @@ pub const PREV_ICON: egui::ImageSource = egui::include_image!("../../assets/pre
|
|||
pub const NEXT_ICON: egui::ImageSource = egui::include_image!("../../assets/next.svg");
|
||||
pub const PLAY_ICON: egui::ImageSource = egui::include_image!("../../assets/play.svg");
|
||||
pub const PAUSE_ICON: egui::ImageSource = egui::include_image!("../../assets/pause.svg");
|
||||
pub const CHECK_ICON: egui::ImageSource = egui::include_image!("../../assets/check.svg");
|
||||
pub const DL_ICON: egui::ImageSource = egui::include_image!("../../assets/download.svg");
|
||||
pub const WARN_ICON: egui::ImageSource = egui::include_image!("../../assets/warning.svg");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![feature(async_closure)]
|
||||
|
||||
use std::{path::{Path, PathBuf}, time::Duration};
|
||||
use std::time::Duration;
|
||||
use xmpd_manifest::{store::JsonStore, Manifest};
|
||||
|
||||
#[macro_use]
|
||||
|
@ -15,10 +15,10 @@ const W_NAME: &str = "xmpd v2.0.0a";
|
|||
|
||||
type Result<T> = anyhow::Result<T>;
|
||||
|
||||
pub fn start(manifest_path: PathBuf) -> Result<()> {
|
||||
pub fn start() -> Result<()> {
|
||||
xmpd_cache::Cache::get()?.init();
|
||||
let options = eframe::NativeOptions::default();
|
||||
let mut state = GuiState::new(&manifest_path)?;
|
||||
let theme = xmpd_settings::Settings::get()?.theme.clone();
|
||||
let mut state = GuiState::new()?;
|
||||
let res = eframe::run_simple_native(W_NAME, options, move |ctx, _frame| {
|
||||
egui_extras::install_image_loaders(ctx);
|
||||
state.windows.clone().draw_all(ctx, &mut state);
|
||||
|
@ -31,20 +31,15 @@ pub fn start(manifest_path: PathBuf) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub enum Message {
|
||||
|
||||
}
|
||||
|
||||
pub struct GuiState {
|
||||
pub manifest: Manifest<JsonStore>,
|
||||
pub windows: windows::Windows
|
||||
pub windows: windows::Windows,
|
||||
}
|
||||
|
||||
impl GuiState {
|
||||
pub fn new(manifest_path: &Path) -> Result<Self> {
|
||||
pub fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
manifest: Manifest::new(manifest_path)?,
|
||||
manifest: Manifest::new(&xmpd_cliargs::CLIARGS.manifest_path())?,
|
||||
windows: windows::Windows::new(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use xmpd_settings::theme::Theme;
|
||||
|
||||
use crate::{components::CompUi, GuiState};
|
||||
use crate::{components::{song_list, CompUi}, GuiState};
|
||||
|
||||
pub fn draw(ctx: &egui::Context, state: &mut GuiState) -> crate::Result<()> {
|
||||
// The central panel the region left after adding TopPanel's and SidePanel's
|
||||
|
@ -22,19 +22,22 @@ pub fn draw(ctx: &egui::Context, state: &mut GuiState) -> crate::Result<()> {
|
|||
let avail = ui.available_size();
|
||||
let main_height = avail.y * 0.91;
|
||||
|
||||
let left_nav_width = (avail.x * 0.25).clamp(0.0, 200.0);
|
||||
let song_list_width = (avail.x - left_nav_width - 35.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_height(main_height);
|
||||
ui.group(|ui| {
|
||||
ui.set_height(main_height);
|
||||
ui.set_max_width((avail.x * 0.25).clamp(0.0, 200.0));
|
||||
ui.set_max_width(left_nav_width);
|
||||
handle_error_ui!(crate::components::left_nav::LeftNav::draw(ui, state));
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
ui.group(|ui| {
|
||||
ui.set_width(avail.x * 0.75);
|
||||
ui.set_width(song_list_width);
|
||||
handle_error_ui!(crate::components::song_list::song_list_nav::SongListNav::draw(ui, state));
|
||||
});
|
||||
ui.group(|ui| {
|
||||
ui.set_width(song_list_width);
|
||||
handle_error_ui!(crate::components::song_list::SongList::draw(ui, state));
|
||||
});
|
||||
});
|
||||
|
@ -58,7 +61,7 @@ fn get_themed_frame(theme: &Theme) -> egui::Frame {
|
|||
egui::Frame::none()
|
||||
.fill(theme.primary_bg_color)
|
||||
.stroke(egui::Stroke::new(
|
||||
1.0,
|
||||
5.0,
|
||||
theme.secondary_bg_color,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,32 +1,37 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use super::Window;
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SettingsW {
|
||||
accent_color: egui::Color32,
|
||||
primary_bg_color: egui::Color32,
|
||||
secondary_bg_color: egui::Color32,
|
||||
text_color: egui::Color32,
|
||||
dim_text_color: egui::Color32,
|
||||
ytdlp_p: String,
|
||||
spotdl_p: String,
|
||||
ffmpeg_p: String,
|
||||
song_fmt: String,
|
||||
}
|
||||
|
||||
impl Default for SettingsW {
|
||||
fn default() -> Self {
|
||||
let def = xmpd_settings::theme::Theme::default();
|
||||
let tooling = xmpd_settings::Settings::get().unwrap().tooling.clone();
|
||||
Self {
|
||||
accent_color: def.accent_color,
|
||||
primary_bg_color: def.primary_bg_color,
|
||||
secondary_bg_color: def.secondary_bg_color,
|
||||
text_color: def.text_color,
|
||||
dim_text_color: def.dim_text_color
|
||||
ytdlp_p: tooling.ytdlp_path.to_string(),
|
||||
spotdl_p: tooling.spotdl_path.to_string(),
|
||||
ffmpeg_p: tooling.ffmpeg_path.to_string(),
|
||||
song_fmt: tooling.song_format
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Window for SettingsW {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
|
||||
let theme = &mut xmpd_settings::Settings::get()?.theme;
|
||||
ui.group(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
{
|
||||
let theme = &mut handle_error_ui!(xmpd_settings::Settings::get()).theme;
|
||||
ui.group(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Theme");
|
||||
|
@ -35,6 +40,41 @@ impl Window for SettingsW {
|
|||
Self::add_theme_button(&mut theme.secondary_bg_color, ui, "Secondary BG");
|
||||
Self::add_theme_button(&mut theme.text_color, ui, "Text");
|
||||
Self::add_theme_button(&mut theme.dim_text_color, ui, "Dim Text");
|
||||
if ui.button("Reset").clicked() {
|
||||
*theme = xmpd_settings::theme::Theme::default();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
{
|
||||
ui.group(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Tooling paths");
|
||||
Self::add_tooling_input(&mut self.ytdlp_p, ui, "stdlp");
|
||||
Self::add_tooling_input(&mut self.spotdl_p, ui, "spotdl");
|
||||
Self::add_tooling_input(&mut self.ffmpeg_p, ui, "ffmpeg");
|
||||
Self::add_tooling_input(&mut self.song_fmt, ui, "Format");
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
ui.with_layout(egui::Layout::bottom_up(egui::Align::RIGHT), |ui| {
|
||||
if ui.button("Save").clicked() {
|
||||
let mut settings = handle_error_ui!(xmpd_settings::Settings::get());
|
||||
if let Ok(p) = camino::Utf8PathBuf::from_str(&self.ytdlp_p) {
|
||||
settings.tooling.ytdlp_path = p;
|
||||
}
|
||||
if let Ok(p) = camino::Utf8PathBuf::from_str(&self.spotdl_p) {
|
||||
settings.tooling.spotdl_path = p;
|
||||
}
|
||||
if let Ok(p) = camino::Utf8PathBuf::from_str(&self.ffmpeg_p) {
|
||||
settings.tooling.ffmpeg_path = p;
|
||||
}
|
||||
settings.tooling.song_format.clone_from(&self.song_fmt);
|
||||
handle_error_ui!(settings.save(None));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Ok(())
|
||||
|
@ -48,4 +88,10 @@ impl SettingsW {
|
|||
ui.color_edit_button_srgba(rf);
|
||||
});
|
||||
}
|
||||
fn add_tooling_input(inp: &mut String, ui: &mut egui::Ui, name: &str) {
|
||||
ui.horizontal(|ui|{
|
||||
ui.label(format!("{name}: "));
|
||||
ui.text_edit_singleline(inp);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ authors.workspace = true
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
camino.workspace = true
|
||||
egui.workspace = true
|
||||
lazy_static.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use std::{path::PathBuf, sync::{Arc, Mutex, MutexGuard}};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use theme::Theme;
|
||||
use tooling::Tooling;
|
||||
|
||||
pub mod theme;
|
||||
pub mod tooling;
|
||||
|
||||
lazy_static::lazy_static!(
|
||||
static ref SETTINGS: Arc<Mutex<Settings>> = Arc::new(Mutex::new(Settings::default()));
|
||||
|
@ -14,7 +16,10 @@ pub type Result<T> = anyhow::Result<T>;
|
|||
pub struct Settings {
|
||||
#[serde(skip)]
|
||||
settings_path: PathBuf,
|
||||
#[serde(default)]
|
||||
pub theme: Theme,
|
||||
#[serde(default)]
|
||||
pub tooling: Tooling,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
|
@ -27,6 +32,11 @@ impl Settings {
|
|||
pub fn load(&mut self, path: Option<PathBuf>) -> Result<()> {
|
||||
let path = path.unwrap_or(self.settings_path.clone());
|
||||
if !path.exists() {
|
||||
let mut dir = path.clone();
|
||||
dir.pop();
|
||||
if !dir.exists() {
|
||||
std::fs::create_dir_all(dir)?;
|
||||
}
|
||||
std::fs::write(&path, "[theme]")?;
|
||||
self.save(Some(path.clone()))?;
|
||||
}
|
||||
|
|
42
xmpd-settings/src/tooling.rs
Normal file
42
xmpd-settings/src/tooling.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Tooling {
|
||||
#[serde(default="Tooling::default_ytdlp_path")]
|
||||
pub ytdlp_path: camino::Utf8PathBuf,
|
||||
#[serde(default="Tooling::default_spotdl_path")]
|
||||
pub spotdl_path: camino::Utf8PathBuf,
|
||||
#[serde(default="Tooling::default_ffmpeg_path")]
|
||||
pub ffmpeg_path: camino::Utf8PathBuf,
|
||||
#[serde(default="Tooling::default_song_format")]
|
||||
pub song_format: String,
|
||||
}
|
||||
|
||||
impl Default for Tooling {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ytdlp_path: Self::default_ytdlp_path(),
|
||||
spotdl_path: Self::default_spotdl_path(),
|
||||
ffmpeg_path: Self::default_ffmpeg_path(),
|
||||
song_format: Self::default_song_format(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tooling {
|
||||
fn default_ytdlp_path() -> camino::Utf8PathBuf {
|
||||
camino::Utf8PathBuf::from_str("yt-dlp").unwrap()
|
||||
}
|
||||
fn default_spotdl_path() -> camino::Utf8PathBuf {
|
||||
camino::Utf8PathBuf::from_str("spotdl").unwrap()
|
||||
}
|
||||
fn default_ffmpeg_path() -> camino::Utf8PathBuf {
|
||||
camino::Utf8PathBuf::from_str("ffmpeg").unwrap()
|
||||
}
|
||||
fn default_song_format() -> String {
|
||||
String::from("flac")
|
||||
}
|
||||
}
|
9
xmpd-tooling/Cargo.toml
Normal file
9
xmpd-tooling/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "xmpd-tooling"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
0
xmpd-tooling/src/lib.rs
Normal file
0
xmpd-tooling/src/lib.rs
Normal file
Loading…
Reference in New Issue
Block a user