use new resolver setup
This commit is contained in:
parent
f36fbac800
commit
b0bdb8f946
|
@ -2,13 +2,10 @@ Source(
|
|||
name: "yt",
|
||||
format: "flac",
|
||||
kind: Shell(
|
||||
cmd: "yt-dlp",
|
||||
cmd: "bash",
|
||||
args: [
|
||||
"-x",
|
||||
"--audio-format", "flac",
|
||||
"--audio-quality", "0",
|
||||
"-o", "${output}",
|
||||
"https://youtube.com/watch?v=${input}"
|
||||
"-c",
|
||||
"yt-dlp -x --audio-format flac --audio-quality 0 -o ${output} https://youtube.com/watch?v=${input} && mv ${output}.flac ${output}",
|
||||
]
|
||||
)
|
||||
)
|
||||
|
|
146
src/main.rs
146
src/main.rs
|
@ -2,6 +2,7 @@
|
|||
extern crate tracing;
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
env, fs,
|
||||
io::{self, BufRead},
|
||||
path::PathBuf,
|
||||
|
@ -10,11 +11,8 @@ use std::{
|
|||
use clap::{Parser, Subcommand};
|
||||
use color_eyre::eyre::{anyhow, bail, Result};
|
||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||
use heck::ToSnakeCase;
|
||||
use resolver::Resolver;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::schema::DlPlaylist;
|
||||
use schema::Playlist;
|
||||
|
||||
mod cache;
|
||||
mod cfg;
|
||||
|
@ -54,6 +52,18 @@ enum Command {
|
|||
},
|
||||
/// Print version information
|
||||
Version,
|
||||
/// Garbage collect store
|
||||
///
|
||||
/// deletes any downloaded files that are no longer referenced
|
||||
/// by any playlists
|
||||
GC {
|
||||
/// directory to "run in"
|
||||
#[arg(long = "in")]
|
||||
run_in: Option<PathBuf>,
|
||||
/// find, but do not remove, unreferenced files
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
|
@ -69,11 +79,10 @@ fn main() -> Result<()> {
|
|||
res.create_dirs()?;
|
||||
log::initialize_logging(Some(res.tmp_file("dmm.log")))?;
|
||||
res.resolve()?;
|
||||
let config = res.out().config.clone();
|
||||
let chosen = {
|
||||
let chosen: Playlist = {
|
||||
let mut scores = vec![];
|
||||
let matcher = SkimMatcherV2::default().ignore_case();
|
||||
for (i, j) in res.out().cache.playlists.iter().enumerate() {
|
||||
for (i, j) in res.out().playlists.iter().enumerate() {
|
||||
if let Some(score) = matcher.fuzzy_match(&j.name, &playlist) {
|
||||
scores.push((score, i));
|
||||
}
|
||||
|
@ -85,16 +94,20 @@ fn main() -> Result<()> {
|
|||
return Ok(());
|
||||
} else {
|
||||
scores.sort_by_key(|score| score.0);
|
||||
let chosen = &res.out().cache.playlists[scores[0].1];
|
||||
chosen
|
||||
let chosen = &res.out().playlists[scores[0].1];
|
||||
chosen.clone()
|
||||
}
|
||||
};
|
||||
let mut app = ui::app::App::new(config, 15.0, chosen.clone())?;
|
||||
let mut app = ui::app::App::new(res, 15.0, chosen)?;
|
||||
app.run()?;
|
||||
}
|
||||
Command::Version => {
|
||||
println!("{}", project_meta::version());
|
||||
}
|
||||
Command::GC { run_in, dry_run } => {
|
||||
log::initialize_logging(None)?;
|
||||
gc(run_in, dry_run)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -153,70 +166,69 @@ fn download(run_in: Option<PathBuf>, name: String) -> Result<()> {
|
|||
}
|
||||
}
|
||||
let src = chosen.clone();
|
||||
let dest = res.dirs().cache.clone();
|
||||
download_playlist(src, dest)?;
|
||||
download_playlist(src, &res.out().cache)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// src: <playlist>.ron file (in playlists/)
|
||||
/// dest: (cache/) directory (a new subdir will be created for this playlist)
|
||||
fn download_playlist(playlist: schema::Playlist, dest: PathBuf) -> Result<()> {
|
||||
let out_dir_name = playlist.name.to_snake_case();
|
||||
let out_dir = dest.join(out_dir_name);
|
||||
if out_dir.try_exists()? {
|
||||
info!("Playlist already exists, checking for changes");
|
||||
let dl_playlist_str = fs::read_to_string(out_dir.join("index.ron"))?;
|
||||
let dl_playlist = ron::from_str::<schema::DlPlaylist>(&dl_playlist_str)?;
|
||||
let diff = dl_playlist.gen_diff(&playlist);
|
||||
if diff.changes.is_empty() {
|
||||
info!("No changes to playlist, nothing to do");
|
||||
return Ok(());
|
||||
fn download_playlist(playlist: schema::Playlist, cache: &cache::CacheDir) -> Result<()> {
|
||||
info!("downloading tracks in playlist {} to cache", playlist.name);
|
||||
for track in &playlist.tracks {
|
||||
info!("downloading {}", track.meta.name);
|
||||
let source = playlist.find_source(&track.src).ok_or(anyhow!(
|
||||
"Could not find source {} for track {}",
|
||||
track.src,
|
||||
track.meta.name
|
||||
))?;
|
||||
let hash = cache::Hash::generate(source, &track.input);
|
||||
if cache.find(hash).is_some() {
|
||||
info!("track exists in cache [skiping]");
|
||||
continue;
|
||||
}
|
||||
diff.display();
|
||||
println!("Apply changes? [y/N]:");
|
||||
let Some(next) = io::stdin().lock().lines().next() else {
|
||||
bail!("Failed to get input");
|
||||
};
|
||||
match next?.as_str() {
|
||||
"y" | "Y" => {}
|
||||
_ => {
|
||||
info!("Aborting");
|
||||
return Ok(());
|
||||
let path = cache.create(hash);
|
||||
source.execute(track.input.clone(), &path)?;
|
||||
debug!("download complete");
|
||||
}
|
||||
info!("Done!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gc(run_in: Option<PathBuf>, dry_run: bool) -> Result<()> {
|
||||
let mut res = Resolver::new(resolve_run_path(run_in)?);
|
||||
res.create_dirs()?;
|
||||
res.resolve()?;
|
||||
let mut hashes = HashSet::new();
|
||||
let mut source_map = HashMap::new();
|
||||
for playlist in &res.out().playlists {
|
||||
for source in playlist.resolved_sources.as_ref().unwrap() {
|
||||
source_map.insert(source.name.clone(), source.clone());
|
||||
}
|
||||
for track in &playlist.tracks {
|
||||
let source = source_map
|
||||
.get(&track.src)
|
||||
.expect("Cannot find source for track");
|
||||
let hash = cache::Hash::generate(source, &track.input);
|
||||
hashes.insert(hash);
|
||||
}
|
||||
}
|
||||
let mut bytes_removed = 0u64;
|
||||
let mut files_removed = 0usize;
|
||||
for entry in res.dirs().cache.read_dir()? {
|
||||
let entry = entry?;
|
||||
let hash = entry
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("path not utf-8")
|
||||
.parse::<cache::Hash>()?;
|
||||
if !hashes.contains(&hash) {
|
||||
info!("deleting {}", hash.to_string());
|
||||
bytes_removed += entry.metadata()?.len();
|
||||
files_removed += 1;
|
||||
if !dry_run {
|
||||
fs::remove_file(entry.path())?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("Downloading playlist {} to {:?}", playlist.name, out_dir);
|
||||
fs::create_dir(&out_dir)?;
|
||||
let mut dl_playlist = DlPlaylist {
|
||||
directory: Default::default(),
|
||||
name: playlist.name.clone(),
|
||||
sources: playlist.resolved_sources.clone().unwrap(),
|
||||
tracks: vec![],
|
||||
};
|
||||
for track in &playlist.tracks {
|
||||
info!("Downloading {}", track.meta.name);
|
||||
let source = playlist.find_source(&track.src).ok_or(anyhow!(
|
||||
"Could not find source {} for track {}",
|
||||
track.src,
|
||||
track.meta.name
|
||||
))?;
|
||||
let uuid = Uuid::new_v4();
|
||||
let path = out_dir.join(uuid.to_string());
|
||||
source.execute(track.input.clone(), &path)?;
|
||||
debug!("Download complete");
|
||||
dl_playlist.tracks.push(schema::DlTrack {
|
||||
track: track.clone(),
|
||||
track_id: uuid,
|
||||
});
|
||||
}
|
||||
let dl_playlist_str = ron::ser::to_string_pretty(
|
||||
&dl_playlist,
|
||||
ron::ser::PrettyConfig::new().struct_names(true),
|
||||
)?;
|
||||
fs::write(out_dir.join("index.ron"), dl_playlist_str.as_bytes())?;
|
||||
info!("Downloading playlist complete");
|
||||
}
|
||||
|
||||
info!("removed {files_removed} entries, freed {bytes_removed} bytes");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@ use std::{
|
|||
use color_eyre::eyre::{anyhow, Result};
|
||||
|
||||
use crate::{
|
||||
cache::CacheDir,
|
||||
cfg::Config,
|
||||
schema::{self, DlPlaylist, Playlist, Source},
|
||||
schema::{self, Playlist, Source},
|
||||
};
|
||||
|
||||
struct State {
|
||||
|
@ -19,12 +20,7 @@ pub struct Output {
|
|||
pub config: Config,
|
||||
pub sources: Vec<Source>,
|
||||
pub playlists: Vec<Playlist>,
|
||||
pub cache: Cache,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Cache {
|
||||
pub playlists: Vec<DlPlaylist>,
|
||||
pub cache: CacheDir,
|
||||
}
|
||||
|
||||
pub struct Directories {
|
||||
|
@ -130,17 +126,7 @@ impl Resolver {
|
|||
}
|
||||
|
||||
{
|
||||
for pl_dir in fs::read_dir(&self.d.cache)?.filter_map(Result::ok) {
|
||||
if pl_dir.file_type()?.is_dir() {
|
||||
let index_path = pl_dir.path().join("index.ron");
|
||||
let index_str = fs::read_to_string(&index_path)?;
|
||||
let mut index = ron::from_str::<schema::DlPlaylist>(&index_str)?;
|
||||
index.directory = pl_dir.path();
|
||||
self.o.cache.playlists.push(index);
|
||||
} else {
|
||||
panic!("{pl_dir:?} in cache is not a directory");
|
||||
}
|
||||
}
|
||||
self.o.cache = CacheDir::new(self.d.cache.clone());
|
||||
}
|
||||
|
||||
self.s.resolved = true;
|
||||
|
|
257
src/schema.rs
257
src/schema.rs
|
@ -1,255 +1,16 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use color_eyre::eyre::{anyhow, bail, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct Link {
|
||||
pub music_directory: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct DlPlaylist {
|
||||
#[serde(skip)]
|
||||
pub directory: PathBuf,
|
||||
pub name: String,
|
||||
/// resolved source
|
||||
pub sources: Vec<Source>,
|
||||
pub tracks: Vec<DlTrack>,
|
||||
}
|
||||
|
||||
impl DlPlaylist {
|
||||
#[allow(dead_code)]
|
||||
pub fn find_source(&self, name: &str) -> Option<&Source> {
|
||||
self.sources.iter().find(|x| x.name == name)
|
||||
}
|
||||
|
||||
pub fn gen_diff(&self, other: &Playlist) -> DlPlaylistDiff {
|
||||
let mut diff = DlPlaylistDiff { changes: vec![] };
|
||||
if self.name != other.name {
|
||||
diff.changes.push(DiffChange::Name {
|
||||
old: self.name.clone(),
|
||||
new: other.name.clone(),
|
||||
});
|
||||
}
|
||||
let mut source_map: HashMap<String, Source> = HashMap::new();
|
||||
// set of sources (by [new] name) that have changed output specification
|
||||
let mut source_changed_output: HashSet<String> = HashSet::new();
|
||||
// map of source names (old name -> new name)
|
||||
let mut source_o2n_names: HashMap<String, String> = HashMap::new();
|
||||
for source in self.sources.clone() {
|
||||
source_map.insert(source.name.clone(), source);
|
||||
}
|
||||
for source in other.resolved_sources.clone().unwrap() {
|
||||
if let Some(old) = source_map.remove(&source.name) {
|
||||
if source.format != old.format {
|
||||
source_changed_output.insert(source.name.clone());
|
||||
diff.changes.push(DiffChange::SourceChangeFormat {
|
||||
name: source.name.clone(),
|
||||
old: old.format.clone(),
|
||||
new: source.format.clone(),
|
||||
})
|
||||
}
|
||||
// if these are not matching, then emit DiffChange::SourceReplaceKind
|
||||
if old.kind != source.kind {
|
||||
source_changed_output.insert(source.name.clone());
|
||||
diff.changes.push(DiffChange::SourceModifyKind {
|
||||
name: source.name.clone(),
|
||||
old: old.kind,
|
||||
new: source.kind,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
diff.changes.push(DiffChange::AddSource { new: source });
|
||||
}
|
||||
}
|
||||
for source in source_map.into_values() {
|
||||
diff.changes.push(DiffChange::DelSource { removed: source });
|
||||
}
|
||||
let mut source_map: HashMap<SourceKind, String> = HashMap::new();
|
||||
for source in self.sources.clone() {
|
||||
source_map.insert(source.kind, source.name);
|
||||
}
|
||||
for source in other.sources.clone() {
|
||||
if let Some(old) = source_map.remove(&source.kind) {
|
||||
if old != source.name {
|
||||
source_o2n_names.insert(old.clone(), source.name.clone());
|
||||
diff.changes.push(DiffChange::SourceChangeName {
|
||||
old,
|
||||
new: source.name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut track_map: HashMap<Meta, Track> = HashMap::new();
|
||||
for track in self.tracks.clone() {
|
||||
track_map.insert(track.track.meta.clone(), track.track.clone());
|
||||
}
|
||||
for track in other.tracks.clone() {
|
||||
if let Some(old) = track_map.remove(&track.meta) {
|
||||
if track.src != old.src || track.input != old.input {
|
||||
diff.changes
|
||||
.push(DiffChange::TrackChangedSource { old, new: track });
|
||||
}
|
||||
} else {
|
||||
diff.changes.push(DiffChange::AddTrack { new: track });
|
||||
}
|
||||
}
|
||||
for track in track_map.into_values() {
|
||||
diff.changes.push(DiffChange::DelTrack { removed: track });
|
||||
}
|
||||
let mut track_map: HashMap<(String, ron::Value), Track> = HashMap::new();
|
||||
for track in self.tracks.clone() {
|
||||
track_map.insert(
|
||||
(track.track.src.clone(), track.track.input.clone()),
|
||||
track.track.clone(),
|
||||
);
|
||||
}
|
||||
for track in other.tracks.clone() {
|
||||
if let Some(old) = track_map.remove(&(track.src.clone(), track.input.clone())) {
|
||||
if old.meta != track.meta {
|
||||
diff.changes
|
||||
.push(DiffChange::TrackLikelyChangedMeta { old, new: track })
|
||||
}
|
||||
}
|
||||
}
|
||||
diff
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct DlPlaylistDiff {
|
||||
pub changes: Vec<DiffChange>,
|
||||
}
|
||||
|
||||
impl DlPlaylistDiff {
|
||||
pub fn display(&self) {
|
||||
println!(" --- playlist diff --- ");
|
||||
for change in &self.changes {
|
||||
use DiffChange::*;
|
||||
match change.clone() {
|
||||
Name { old, new } => println!(" M [playlist/name]\t {old} -> {new}"),
|
||||
AddSource { new } => println!(
|
||||
" + [source]\t {name} | output: {format} | kind: {variant}",
|
||||
name = new.name,
|
||||
format = new.format,
|
||||
variant = match new.kind {
|
||||
SourceKind::Shell { .. } => "shell",
|
||||
}
|
||||
),
|
||||
DelSource { removed } => println!(" - [source]\t {name}", name = removed.name),
|
||||
SourceChangeName { old, new } => println!(" M [source/name]\t {old} -> {new}"),
|
||||
SourceChangeFormat { name, old, new } => {
|
||||
println!(" M [source/format]\t of source {name}: {old} -> {new}")
|
||||
}
|
||||
SourceReplaceKind { name, old, new } => {
|
||||
println!(
|
||||
" M [source/kind]\t of source {name}: {old} -> {new}",
|
||||
old = match old {
|
||||
SourceKind::Shell { .. } => "shell",
|
||||
},
|
||||
new = match new {
|
||||
SourceKind::Shell { .. } => "shell",
|
||||
}
|
||||
)
|
||||
}
|
||||
SourceModifyKind { name, old, new } => match (old, new) {
|
||||
(
|
||||
SourceKind::Shell {
|
||||
cmd: old_cmd,
|
||||
args: old_args,
|
||||
},
|
||||
SourceKind::Shell { cmd, args },
|
||||
) => {
|
||||
if old_cmd != cmd && old_args != args {
|
||||
println!(" M [source/kind/all]\t of source {name}:\n M [cmd]\t {old_cmd} -> {cmd}\n M [args]\t {old_args:?} -> {args:?}")
|
||||
} else if old_cmd != cmd {
|
||||
println!(" M [source/kind/-cmd]\t of source {name}: {old_cmd} -> {cmd}")
|
||||
} else if old_args != args {
|
||||
println!(" M [source/kind/-args]\t of source {name}: {old_args:?} -> {args:?}")
|
||||
}
|
||||
}
|
||||
},
|
||||
AddTrack { new } => println!(
|
||||
" + [track]\t '{name}' by {artist}",
|
||||
name = new.meta.name,
|
||||
artist = new.meta.artist
|
||||
),
|
||||
DelTrack { removed } => println!(
|
||||
" - [track]\t '{name}' by {artist}",
|
||||
name = removed.meta.name,
|
||||
artist = removed.meta.artist
|
||||
),
|
||||
TrackLikelyChangedMeta { old, new } => println!(" M [track/rename]\n M [track/rename/name]\t {} -> {}\n M [track/rename/artist]\t {} -> {}", old.meta.name, new.meta.name, old.meta.artist, new.meta.artist),
|
||||
TrackChangedSource { old, new } => println!(" M [track/source]\t track '{name}' by {artist}: {} with {:?} -> {} with {:?}", old.src, old.input, new.src, new.input, name=old.meta.name, artist=old.meta.artist,),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub enum DiffChange {
|
||||
Name {
|
||||
old: String,
|
||||
new: String,
|
||||
},
|
||||
AddSource {
|
||||
new: Source,
|
||||
},
|
||||
DelSource {
|
||||
removed: Source,
|
||||
},
|
||||
SourceChangeName {
|
||||
old: String,
|
||||
new: String,
|
||||
},
|
||||
SourceChangeFormat {
|
||||
name: String,
|
||||
old: String,
|
||||
new: String,
|
||||
},
|
||||
SourceReplaceKind {
|
||||
name: String,
|
||||
old: SourceKind,
|
||||
new: SourceKind,
|
||||
},
|
||||
/// modification to the *content* of SourceKind (variant is the same)
|
||||
SourceModifyKind {
|
||||
name: String,
|
||||
old: SourceKind,
|
||||
new: SourceKind,
|
||||
},
|
||||
AddTrack {
|
||||
new: Track,
|
||||
},
|
||||
DelTrack {
|
||||
removed: Track,
|
||||
},
|
||||
/// the metadata of a track (name or author) changed, but the `src` and `input` did not.
|
||||
/// this likely means that the track was re-named
|
||||
TrackLikelyChangedMeta {
|
||||
old: Track,
|
||||
new: Track,
|
||||
},
|
||||
/// the metadata of a track (name/author) is the same, but the `src` and/or `input` changed.
|
||||
TrackChangedSource {
|
||||
old: Track,
|
||||
new: Track,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct DlTrack {
|
||||
pub track: Track,
|
||||
pub track_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct Playlist {
|
||||
#[serde(skip)]
|
||||
|
@ -294,18 +55,12 @@ impl Source {
|
|||
let args = args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
Ok(if arg.contains("${input}") {
|
||||
arg.replace("${input}", &input)
|
||||
} else if arg.contains("${output}") {
|
||||
arg.replace(
|
||||
"${output}",
|
||||
output
|
||||
.to_str()
|
||||
.ok_or(anyhow!("output path not valid UTF-8"))?,
|
||||
)
|
||||
} else {
|
||||
arg.to_owned()
|
||||
})
|
||||
Ok(arg.replace("${input}", &input).replace(
|
||||
"${output}",
|
||||
output
|
||||
.to_str()
|
||||
.ok_or(anyhow!("output path not valid UTF-8"))?,
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<String>>>()?;
|
||||
let res = Command::new(cmd).args(args).status()?;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::prelude::Rect;
|
||||
|
@ -8,7 +10,7 @@ use super::{
|
|||
mode::Mode,
|
||||
tui,
|
||||
};
|
||||
use crate::{cfg::Config, schema::DlPlaylist};
|
||||
use crate::{resolver::Resolver, schema::Playlist};
|
||||
|
||||
pub struct App {
|
||||
pub frame_rate: f64,
|
||||
|
@ -16,12 +18,13 @@ pub struct App {
|
|||
pub should_quit: bool,
|
||||
pub mode: Mode,
|
||||
pub last_tick_key_events: Vec<KeyEvent>,
|
||||
pub config: Config,
|
||||
pub resolver: Arc<Resolver>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(config: Config, frame_rate: f64, pl: DlPlaylist) -> Result<Self> {
|
||||
let home = Home::new(pl)?;
|
||||
pub fn new(res: Resolver, frame_rate: f64, pl: Playlist) -> Result<Self> {
|
||||
let resolver = Arc::new(res);
|
||||
let home = Home::new(pl.clone(), resolver.clone())?;
|
||||
let fps = FpsCounter::default();
|
||||
let mode = Mode::Home;
|
||||
Ok(Self {
|
||||
|
@ -30,7 +33,7 @@ impl App {
|
|||
should_quit: false,
|
||||
mode,
|
||||
last_tick_key_events: Vec::new(),
|
||||
config,
|
||||
resolver,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -45,7 +48,7 @@ impl App {
|
|||
}
|
||||
|
||||
for component in self.components.iter_mut() {
|
||||
component.register_config_handler(self.config.clone())?;
|
||||
component.register_config_handler(self.resolver.out().config.clone())?;
|
||||
}
|
||||
|
||||
for component in self.components.iter_mut() {
|
||||
|
@ -59,7 +62,7 @@ impl App {
|
|||
tui::Event::Render => action_tx.send(Action::Render)?,
|
||||
tui::Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
|
||||
tui::Event::Key(key) => {
|
||||
if let Some(keymap) = self.config.keybinds.get(&self.mode) {
|
||||
if let Some(keymap) = self.resolver.out().config.keybinds.get(&self.mode) {
|
||||
if let Some(action) = keymap.get(&vec![key]) {
|
||||
log::info!("Got action: {action:?}");
|
||||
action_tx.send(action.clone())?;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{cmp, fs, iter, path::PathBuf, sync::Arc};
|
||||
use std::{cmp, fs, iter, sync::Arc};
|
||||
|
||||
use color_eyre::eyre::{anyhow, bail, Result};
|
||||
use cpal::traits::{DeviceTrait, HostTrait};
|
||||
|
@ -9,9 +9,11 @@ use ratatui::{prelude::*, widgets::*};
|
|||
|
||||
use super::{Component, Frame};
|
||||
use crate::{
|
||||
cache,
|
||||
cfg::{self, Config},
|
||||
player2::{self, SingleTrackPlayer},
|
||||
schema::DlPlaylist,
|
||||
resolver::Resolver,
|
||||
schema::Playlist,
|
||||
ui::{action::Action, mode::Mode, symbol},
|
||||
};
|
||||
|
||||
|
@ -51,8 +53,7 @@ pub struct Home {
|
|||
command_tx: Option<Sender<Action>>,
|
||||
// info bar
|
||||
c_track_idx: usize,
|
||||
playlist: DlPlaylist,
|
||||
playlist_dir: PathBuf,
|
||||
playlist: Playlist,
|
||||
// player
|
||||
player: SingleTrackPlayer,
|
||||
sel_method: TrackSelectionMethod,
|
||||
|
@ -65,11 +66,13 @@ pub struct Home {
|
|||
t_list_state: ListState,
|
||||
/// jump to track # when receiving TrackComplete (takes precedence over normal track selection)
|
||||
jump_on_track_complete: Option<usize>,
|
||||
// resolver
|
||||
resolver: Arc<Resolver>,
|
||||
}
|
||||
|
||||
impl Home {
|
||||
pub fn new(dl_pl: DlPlaylist) -> Result<Self> {
|
||||
info!("Loaded playlist {name}", name = dl_pl.name);
|
||||
pub fn new(pl: Playlist, res: Arc<Resolver>) -> Result<Self> {
|
||||
info!("Loaded playlist {name}", name = pl.name);
|
||||
|
||||
debug!("Initializing audio backend");
|
||||
let host = cpal::default_host();
|
||||
|
@ -86,12 +89,10 @@ impl Home {
|
|||
};
|
||||
let player = SingleTrackPlayer::new(Arc::new(config), Arc::new(device))?;
|
||||
|
||||
let pl_dir = dl_pl.directory.clone();
|
||||
Ok(Self {
|
||||
command_tx: None,
|
||||
c_track_idx: 0,
|
||||
playlist: dl_pl,
|
||||
playlist_dir: pl_dir,
|
||||
playlist: pl,
|
||||
player,
|
||||
sel_method: TrackSelectionMethod::Sequential,
|
||||
repeat: Repeat::RepeatPlaylist,
|
||||
|
@ -99,6 +100,7 @@ impl Home {
|
|||
cfg: Config::default(),
|
||||
t_list_state: ListState::default().with_selected(Some(0)),
|
||||
jump_on_track_complete: None,
|
||||
resolver: res,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -140,21 +142,24 @@ impl Home {
|
|||
return Ok(());
|
||||
}
|
||||
let track = self.playlist.tracks.get(self.c_track_idx).unwrap();
|
||||
let track_path =
|
||||
self.playlist_dir
|
||||
.read_dir()?
|
||||
.find(|res| {
|
||||
res.as_ref().is_ok_and(|entry| {
|
||||
entry.path().file_stem().is_some_and(|name| {
|
||||
name.to_string_lossy() == track.track_id.to_string()
|
||||
})
|
||||
})
|
||||
})
|
||||
.ok_or(anyhow!("BUG: could not file file for downloaded track"))??
|
||||
.path();
|
||||
let hash = cache::Hash::generate(
|
||||
self.resolver
|
||||
.out()
|
||||
.sources
|
||||
.iter()
|
||||
.find(|x| x.name == track.src)
|
||||
.ok_or(anyhow!("could not find track source"))?,
|
||||
&track.input,
|
||||
);
|
||||
let track_path = self
|
||||
.resolver
|
||||
.out()
|
||||
.cache
|
||||
.find(hash)
|
||||
.ok_or(anyhow!("could not find file for track!"))?;
|
||||
let track_fmt = self
|
||||
.playlist
|
||||
.find_source(&track.track.src)
|
||||
.find_source(&track.src)
|
||||
.unwrap()
|
||||
.format
|
||||
.clone();
|
||||
|
@ -204,8 +209,8 @@ impl Component for Home {
|
|||
.summary("DMM Player")
|
||||
.body(&format!(
|
||||
"Now Playing: {name}\nby {artist}",
|
||||
name = track.track.meta.name,
|
||||
artist = track.track.meta.artist
|
||||
name = track.meta.name,
|
||||
artist = track.meta.artist
|
||||
))
|
||||
.show()?;
|
||||
}
|
||||
|
@ -338,7 +343,6 @@ impl Component for Home {
|
|||
.into(),
|
||||
"│".fg(Color::Yellow),
|
||||
self.playlist.tracks[self.c_track_idx]
|
||||
.track
|
||||
.meta
|
||||
.name
|
||||
.clone()
|
||||
|
@ -379,7 +383,7 @@ impl Component for Home {
|
|||
);
|
||||
f.render_widget(playlist, info_layout[0]);
|
||||
|
||||
let sel_track = &self.playlist.tracks[self.t_list_state.selected().unwrap()].track;
|
||||
let sel_track = &self.playlist.tracks[self.t_list_state.selected().unwrap()];
|
||||
let track = Paragraph::new(vec![
|
||||
Line::from(sel_track.meta.name.clone().italic()),
|
||||
Line::from(vec!["by: ".bold(), sel_track.meta.artist.clone().into()]),
|
||||
|
@ -449,7 +453,7 @@ impl Component for Home {
|
|||
},
|
||||
i.to_string().into(),
|
||||
": ".into(),
|
||||
track.track.meta.name.clone().italic(),
|
||||
track.meta.name.clone().italic(),
|
||||
]))
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
|
|
Loading…
Reference in New Issue