Added Playlist imports, refactored part of the controller 🍄

This commit is contained in:
MrDulfin 2024-12-25 02:38:01 -05:00
parent 87965ef6da
commit 417666e21f
11 changed files with 295 additions and 172 deletions

2
.gitignore vendored
View file

@ -29,3 +29,5 @@ dist-ssr
target
Cargo.lock
.cargo
test-config

View file

@ -37,7 +37,7 @@ crossbeam = "0.8.2"
quick-xml = "0.31.0"
leb128 = "0.2.5"
urlencoding = "2.1.3"
m3u8-rs = "5.0.5"
m3u8-rs = "6.0.0"
thiserror = "1.0.56"
uuid = { version = "1.6.1", features = ["v4", "serde"] }
serde_json = "1.0.111"
@ -54,3 +54,5 @@ text_io = "0.1.12"
tokio = { version = "1.40.0", features = ["macros", "rt"] }
async-channel = "2.3.1"
ciborium = "0.2.2"
itertools = "0.13.0"
directories = "5.0.1"

View file

@ -3,11 +3,13 @@
//! other functions
#![allow(while_true)]
use itertools::Itertools;
use kushi::{Queue, QueueItemType};
use kushi::{QueueError, QueueItem};
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::marker::PhantomData;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use thiserror::Error;
use uuid::Uuid;
@ -15,6 +17,7 @@ use uuid::Uuid;
use crate::config::ConfigError;
use crate::music_player::player::{Player, PlayerError};
use crate::music_storage::library::Song;
use crate::music_storage::playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem};
use crate::{config::Config, music_storage::library::MusicLibrary};
use super::queue::{QueueAlbum, QueueSong};
@ -87,28 +90,20 @@ pub enum LibraryCommand {
Song(Uuid),
AllSongs,
GetLibrary,
Playlist(Uuid),
ImportM3UPlayList(PathBuf)
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum LibraryResponse {
Song(Song),
Ok,
Song(Song, usize),
AllSongs(Vec<Song>),
Library(MusicLibrary),
Playlist(ExternalPlaylist),
ImportM3UPlayList(Uuid, String)
}
#[derive(Debug, PartialEq, PartialOrd, Clone)]
enum InnerLibraryCommand {
Song(Uuid),
AllSongs,
}
#[derive(Debug, PartialEq, Clone)]
enum InnerLibraryResponse<'a> {
Song(&'a Song, usize),
AllSongs(&'a Vec<Song>),
}
#[derive(Debug, PartialEq, Clone)]
pub enum QueueCommand {
Append(QueueItem<QueueSong, QueueAlbum>, bool),
@ -134,7 +129,10 @@ pub struct ControllerInput {
MailMan<PlayerCommand, PlayerResponse>,
MailMan<PlayerResponse, PlayerCommand>,
),
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
lib_mail: (
MailMan<LibraryCommand, LibraryResponse>,
MailMan<LibraryResponse, LibraryCommand>
),
queue_mail: (
MailMan<QueueCommand, QueueResponse>,
MailMan<QueueResponse, QueueCommand>
@ -157,13 +155,13 @@ impl ControllerHandle {
(
ControllerHandle {
lib_mail: lib_mail.0,
lib_mail: lib_mail.0.clone(),
player_mail: player_mail.0.clone(),
queue_mail: queue_mail.0.clone()
},
ControllerInput {
player_mail,
lib_mail: lib_mail.1,
lib_mail: lib_mail,
queue_mail: queue_mail,
library,
config
@ -203,7 +201,6 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
// true,
// );
// }
let inner_lib_mail = MailMan::double();
let queue = queue;
std::thread::scope(|scope| {
@ -215,14 +212,14 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
let player = Arc::new(RwLock::new(P::new().unwrap()));
let _player = player.clone();
let _inner_lib_mail = inner_lib_mail.0.clone();
let _lib_mail = lib_mail.0.clone();
scope
.spawn(async move {
Controller::<P>::player_command_loop(
_player,
player_mail.1,
queue_mail.0,
_inner_lib_mail
_lib_mail,
)
.await
.unwrap();
@ -235,12 +232,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
});
scope
.spawn(async {
Controller::<P>::inner_library_loop(inner_lib_mail.1, &mut library).await
.unwrap()
});
scope
.spawn(async {
Controller::<P>::outer_library_loop(lib_mail, inner_lib_mail.0)
Controller::<P>::library_loop(lib_mail.1, &mut library)
.await
.unwrap();
});
@ -265,7 +257,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
player: Arc<RwLock<P>>,
player_mail: MailMan<PlayerResponse, PlayerCommand>,
queue_mail: MailMan<QueueCommand, QueueResponse>,
inner_lib_mail: MailMan<InnerLibraryCommand, InnerLibraryResponse<'c>>
lib_mail: MailMan<LibraryCommand, LibraryResponse>,
) -> Result<(), ()> {
let mut first = true;
while true {
@ -306,13 +298,13 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
// Append next song in library
inner_lib_mail.send(InnerLibraryCommand::AllSongs).await.unwrap();
lib_mail.send(LibraryCommand::AllSongs).await.unwrap();
let InnerLibraryResponse::AllSongs(songs) = inner_lib_mail.recv().await.unwrap() else {
let LibraryResponse::AllSongs(songs) = lib_mail.recv().await.unwrap() else {
unreachable!()
};
inner_lib_mail.send(InnerLibraryCommand::Song(np_song.song.uuid.clone())).await.unwrap();
let InnerLibraryResponse::Song(_, i) = inner_lib_mail.recv().await.unwrap() else {
lib_mail.send(LibraryCommand::Song(np_song.song.uuid.clone())).await.unwrap();
let LibraryResponse::Song(_, i) = lib_mail.recv().await.unwrap() else {
unreachable!()
};
if let Some(song) = songs.get(i + 49) {
@ -373,8 +365,8 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
}
PlayerCommand::PlayNow(uuid, location) => {
// TODO: This assumes the uuid doesn't point to an album. we've been over this.
inner_lib_mail.send(InnerLibraryCommand::Song(uuid)).await.unwrap();
let InnerLibraryResponse::Song(song, index) = inner_lib_mail.recv().await.unwrap() else {
lib_mail.send(LibraryCommand::Song(uuid)).await.unwrap();
let LibraryResponse::Song(song, index) = lib_mail.recv().await.unwrap() else {
unreachable!()
};
queue_mail.send(QueueCommand::Clear).await.unwrap();
@ -392,8 +384,8 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
// ...
// let's just pretend I figured that out already
inner_lib_mail.send(InnerLibraryCommand::AllSongs).await.unwrap();
let InnerLibraryResponse::AllSongs(songs) = inner_lib_mail.recv().await.unwrap() else {
lib_mail.send(LibraryCommand::AllSongs).await.unwrap();
let LibraryResponse::AllSongs(songs) = lib_mail.recv().await.unwrap() else {
unreachable!()
};
@ -419,59 +411,32 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
Ok(())
}
async fn outer_library_loop(
async fn library_loop(
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
inner_lib_mail: MailMan<InnerLibraryCommand, InnerLibraryResponse<'c>>,
) -> Result<(), ()> {
while true {
match lib_mail.recv().await.unwrap() {
LibraryCommand::Song(uuid) => {
inner_lib_mail
.send(InnerLibraryCommand::Song(uuid))
.await
.unwrap();
let InnerLibraryResponse::Song(song, i) = inner_lib_mail.recv().await.unwrap() else {
unimplemented!();
};
lib_mail.send(LibraryResponse::Song(song.clone())).await.unwrap();
}
LibraryCommand::AllSongs => {
inner_lib_mail
.send(InnerLibraryCommand::AllSongs)
.await
.unwrap();
let x = inner_lib_mail.recv().await.unwrap();
if let InnerLibraryResponse::AllSongs(songs) = x {
lib_mail.send(LibraryResponse::AllSongs(songs.clone())).await.unwrap();
} else {
unreachable!()
}
},
_ => { todo!() }
}
}
Ok(())
}
async fn inner_library_loop(
lib_mail: MailMan<InnerLibraryResponse<'c>, InnerLibraryCommand>,
library: &'c mut MusicLibrary,
) -> Result<(), ()> {
while true {
match lib_mail.recv().await.unwrap() {
InnerLibraryCommand::Song(uuid) => {
let (song, i): (&'c Song, usize) = library.query_uuid(&uuid).unwrap();
lib_mail
.send(InnerLibraryResponse::Song(song, i))
.await
.unwrap();
LibraryCommand::Song(uuid) => {
let (song, i) = library.query_uuid(&uuid).unwrap();
lib_mail.send(LibraryResponse::Song(song.clone(), i)).await.unwrap();
}
InnerLibraryCommand::AllSongs => {
let songs: &'c Vec<Song> = &library.library;
lib_mail.send(InnerLibraryResponse::AllSongs(songs))
.await
.unwrap();
LibraryCommand::AllSongs => {
lib_mail.send(LibraryResponse::AllSongs(library.library.clone())).await.unwrap();
},
LibraryCommand::Playlist(uuid) => {
let playlist = library.query_playlist_uuid(&uuid).unwrap();
lib_mail.send(LibraryResponse::Playlist(ExternalPlaylist::from_playlist(playlist, &library))).await.unwrap();
}
LibraryCommand::ImportM3UPlayList(path) => {
let playlist = Playlist::from_m3u(path, library).unwrap();
let uuid = playlist.uuid.clone();
let name = playlist.title.clone();
library.playlists.items.push(PlaylistFolderItem::List(playlist));
lib_mail.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap();
}
_ => { todo!() }
}
}
Ok(())

View file

@ -2,6 +2,7 @@ use super::playlist::{Playlist, PlaylistFolder};
// Crate things
use super::utils::{find_images, normalize, read_file, write_file};
use crate::config::Config;
use crate::music_storage::playlist::PlaylistFolderItem;
use std::cmp::Ordering;
// Various std things
@ -687,7 +688,7 @@ impl AlbumTrack {
}
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MusicLibrary {
pub name: String,
pub uuid: Uuid,
@ -1237,8 +1238,12 @@ impl MusicLibrary {
Ok(albums)
}
pub fn query_playlist_uuid(&self, uuid: &Uuid) -> Result<Playlist, Box<dyn Error>> {
todo!()
pub fn query_playlist_uuid(&self, uuid: &Uuid) -> Option<&Playlist> {
self.playlists.query_uuid(uuid)
}
pub fn push_playlist(&mut self, playlist: PlaylistFolderItem) {
self.playlists.items.push(playlist);
}
}

View file

@ -1,4 +1,5 @@
use std::error::Error;
use std::path::Path;
use std::{
fs::File,
io::Read,
@ -10,6 +11,7 @@ use std::time::Duration;
// use chrono::Duration;
use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
@ -28,8 +30,8 @@ nest! {
#[derive(Debug, Clone, Deserialize, Serialize)]*
#[derive(Default)]
pub struct PlaylistFolder {
name: String,
items: Vec<
pub(crate) name: String,
pub(crate) items: Vec<
pub enum PlaylistFolderItem {
Folder(PlaylistFolder),
List(Playlist)
@ -38,15 +40,29 @@ nest! {
}
}
impl PlaylistFolder {
pub fn query_uuid(&self, uuid: &Uuid) -> Option<&Playlist> {
for item in &self.items {
match item {
PlaylistFolderItem::Folder(folder) => return folder.query_uuid(uuid),
PlaylistFolderItem::List(ref playlist) => if &playlist.uuid == uuid {
return Some(&playlist);
}
}
}
None
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Playlist {
uuid: Uuid,
title: String,
cover: Option<AlbumArt>,
tracks: Vec<Uuid>,
sort_order: SortOrder,
play_count: i32,
play_time: Duration,
pub(crate) uuid: Uuid,
pub(crate) title: String,
pub(crate) cover: Option<AlbumArt>,
pub(crate) tracks: Vec<Uuid>,
pub(crate) sort_order: SortOrder,
pub(crate) play_count: i32,
pub(crate) play_time: Duration,
}
impl Playlist {
@ -115,7 +131,7 @@ impl Playlist {
super::utils::read_file(PathBuf::from(path))
}
pub fn to_m3u8(
pub fn to_m3u(
&mut self,
lib: Arc<RwLock<MusicLibrary>>,
location: &str,
@ -146,9 +162,9 @@ impl Playlist {
})
.collect::<Vec<MediaSegment>>();
let m3u8 = MediaPlaylist {
let m3u = MediaPlaylist {
version: Some(6),
target_duration: 3.0,
target_duration: 3,
media_sequence: 338559,
discontinuity_sequence: 1234,
end_list: true,
@ -163,18 +179,15 @@ impl Playlist {
.truncate(true)
.write(true)
.open(location)?;
m3u8.write_to(&mut file)?;
m3u.write_to(&mut file)?;
Ok(())
}
pub fn from_m3u8(
path: &str,
lib: Arc<RwLock<MusicLibrary>>,
pub fn from_m3u(
m3u_path: impl AsRef<Path>,
lib: &mut MusicLibrary,
) -> Result<Playlist, Box<dyn Error>> {
let mut file = match File::open(path) {
Ok(file) => file,
Err(e) => return Err(e.into()),
};
let mut file = File::open(&m3u_path)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).unwrap();
@ -182,7 +195,7 @@ impl Playlist {
let playlist = match parsed {
Result::Ok((_, playlist)) => playlist,
Result::Err(e) => panic!("Parsing error: \n{}", e),
Result::Err(e) => panic!("Parsing error\n{e}"),
};
match playlist {
@ -192,28 +205,39 @@ impl Playlist {
List2::MediaPlaylist(playlist_) => {
let mut uuids = Vec::new();
for seg in playlist_.segments {
let path_ = PathBuf::from(seg.uri.to_owned());
let mut lib = lib.write().unwrap();
let seg_path = seg.uri.to_owned();
let uuid = if let Some((song, _)) = lib.query_uri(&URI::Local(path_.clone())) {
let song_path = if let Ok(path) = PathBuf::from(&seg_path).canonicalize() {
path
} else {
println!("{seg_path}");
continue;
};
let uuid = if let Some((song, _)) = lib.query_uri(&URI::Local(song_path.clone())) {
song.uuid
} else {
let song_ = Song::from_file(&path_)?;
let song_: Song = match Song::from_file(&song_path) {
Ok(s) => s,
Err(e) => panic!("{e}\npath: {}", song_path.display())
};
let uuid = song_.uuid.to_owned();
lib.add_song(song_)?;
_ = lib.add_song(song_); // TODO: Add proper error handling with Library
uuid
};
uuids.push(uuid);
}
let mut playlist = Playlist::new();
let path: &str = m3u_path.as_ref().to_str().unwrap();
#[cfg(target_family = "windows")]
{
playlist.title = path
.split('\\')
.last()
.unwrap_or_default()
.strip_suffix(".m3u8")
.strip_suffix(".m3u")
.unwrap_or_default()
.to_string();
}
@ -223,7 +247,7 @@ impl Playlist {
.split("/")
.last()
.unwrap_or_default()
.strip_suffix(".m3u8")
.strip_suffix(".m3u")
.unwrap_or_default()
.to_string();
}
@ -313,42 +337,72 @@ impl Default for Playlist {
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ExternalPlaylist {
pub uuid: Uuid,
pub title: String,
pub tracks: Vec<Song>,
pub sort_order: SortOrder,
pub play_count: i32,
pub play_time: Duration,
}
impl ExternalPlaylist {
pub(crate) fn from_playlist(playlist: &Playlist, library: &MusicLibrary) -> Self {
let tracks: Vec<Song> = playlist.tracks.iter().filter_map(|uuid| {
library.query_uuid(uuid).map(|res| res.0.clone())
}).collect_vec();
Self {
uuid: playlist.uuid.clone(),
title: playlist.title.clone(),
tracks,
sort_order: playlist.sort_order.clone(),
play_count: playlist.play_count,
play_time: playlist.play_time
}
}
}
#[cfg(test)]
mod test_super {
use super::*;
use crate::config::tests::read_config_lib;
use crate::config::tests::{new_config_lib, read_config_lib};
#[test]
fn list_to_m3u8() {
fn list_to_m3u() {
let (_, lib) = read_config_lib();
let mut playlist = Playlist::new();
let tracks = lib.library.iter().map(|track| track.uuid).collect();
playlist.set_tracks(tracks);
_ = playlist.to_m3u8(
playlist.to_m3u(
Arc::new(RwLock::from(lib)),
".\\test-config\\playlists\\playlist.m3u8",
);
}
fn m3u8_to_list() -> Playlist {
let (_, lib) = read_config_lib();
let arc = Arc::new(RwLock::from(lib));
let playlist =
Playlist::from_m3u8(".\\test-config\\playlists\\playlist.m3u8", arc).unwrap();
_ = playlist.to_file(".\\test-config\\playlists\\playlist");
dbg!(playlist)
".\\test-config\\playlists\\playlist.m3u",
).unwrap();
}
#[test]
fn out_queue_sort() {
let (_, lib) = read_config_lib();
let mut list = m3u8_to_list();
list.sort_order = SortOrder::Tag(vec![Tag::Album]);
fn m3u_to_list() {
let (_, mut lib) = read_config_lib();
let songs = &list.out_tracks(Arc::new(RwLock::from(lib)));
let playlist =
Playlist::from_m3u("F:\\Music\\Mp3\\Music Main\\Playlists\\Nanahira.m3u", &mut lib).unwrap();
dbg!(songs);
_ = playlist.to_file(".\\test-config\\playlists\\playlist");
dbg!(&playlist, playlist.tracks.len());
}
// #[test]
// fn out_queue_sort() {
// let (_, lib) = read_config_lib();
// let mut list = m3u_to_list();
// list.sort_order = SortOrder::Tag(vec![Tag::Album]);
// let songs = &list.out_tracks(Arc::new(RwLock::from(lib)));
// dbg!(songs);
// }
}

View file

@ -450,7 +450,7 @@ impl<
use thiserror::Error;
#[derive(Error, Debug, PartialEq, PartialOrd, Clone)]
#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Clone)]
pub enum QueueError {
#[error("Index out of bounds! Index {index} is over len {len}")]
OutOfBounds { index: usize, len: usize },

View file

@ -33,6 +33,7 @@ mime = "0.3.17"
file-format = "0.26.0"
chrono = { version = "0.4.38", features = ["serde"] }
itertools = "0.13.0"
rfd = "0.15.1"
[features]
default = [ "custom-protocol" ]

View file

@ -10,7 +10,7 @@ use crate::wrappers::_Song;
#[tauri::command]
pub async fn add_song_to_queue(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> {
ctrl_handle.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(uuid)).await.unwrap();
let LibraryResponse::Song(song) = ctrl_handle.lib_mail.recv().await.unwrap() else {
let LibraryResponse::Song(song, _) = ctrl_handle.lib_mail.recv().await.unwrap() else {
unreachable!()
};
ctrl_handle.queue_mail.send(dmp_core::music_controller::controller::QueueCommand::Append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location })), true)).await.unwrap();

View file

@ -6,7 +6,7 @@ use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{C
use tauri::{http::Response, Emitter, Manager, State, Url, WebviewWindowBuilder, Wry};
use uuid::Uuid;
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue};
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue, import_playlist, get_playlist};
pub mod wrappers;
pub mod commands;
@ -74,6 +74,8 @@ pub fn run() {
get_queue,
add_song_to_queue,
play_now,
import_playlist,
get_playlist,
]).manage(ConfigRx(rx))
.manage(LibRx(lib_rx))
.manage(HandleTx(handle_tx))
@ -94,7 +96,7 @@ pub fn run() {
futures::executor::block_on(async move {
let controller = ctx.app_handle().state::<ControllerHandle>();
controller.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(Uuid::parse_str(query.as_str()).unwrap())).await.unwrap();
let LibraryResponse::Song(song) = controller.lib_mail.recv().await.unwrap() else { unreachable!() };
let LibraryResponse::Song(song, _) = controller.lib_mail.recv().await.unwrap() else { unreachable!() };
song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec())
})};
res.respond(

View file

@ -1,14 +1,16 @@
use std::collections::BTreeMap;
use std::{collections::BTreeMap, path::PathBuf};
use chrono::{DateTime, Utc, serde::ts_milliseconds_option};
use crossbeam::channel::Sender;
use dmp_core::{music_controller::controller::{ControllerHandle, LibraryCommand, LibraryResponse, PlayerResponse, QueueCommand, QueueResponse}, music_storage::library::{BannedType, Song, URI}};
use dmp_core::{music_controller::controller::{ControllerHandle, LibraryCommand, LibraryResponse, PlayerResponse, QueueCommand, QueueResponse}, music_storage::library::{BannedType, Song, Tag, URI}};
use itertools::Itertools;
use kushi::QueueItemType;
use serde::Serialize;
use tauri::{ipc::Response, AppHandle, Emitter, State, Wry};
use uuid::Uuid;
use crate::commands;
pub struct ArtworkRx(pub Sender<Vec<u8>>);
#[tauri::command]
@ -130,6 +132,7 @@ impl From<&Song> for _Song {
#[tauri::command]
pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec<_Song>, String> {
ctrl_handle.lib_mail.send(LibraryCommand::AllSongs).await.unwrap();
println!("getting library");
let LibraryResponse::AllSongs(songs) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
let _songs = songs.iter().map(|song| _Song::from(song)).collect::<Vec<_>>();
@ -138,9 +141,40 @@ pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec
}
#[tauri::command]
pub async fn get_song(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
ctrl_handle.lib_mail.send(LibraryCommand::Song(Uuid::default())).await.unwrap();
let LibraryResponse::Song(_) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
println!("got songs");
Ok(())
pub async fn get_playlist(ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid) -> Result<Vec<_Song>, String> {
ctrl_handle.lib_mail.send(LibraryCommand::Playlist(uuid)).await.unwrap();
let LibraryResponse::Playlist(playlist) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
let songs = playlist.tracks.iter().map(|song| _Song::from(song)).collect::<Vec<_>>();
println!("Got Playlist {}, len {}", playlist.title, playlist.tracks.len());
Ok(songs)
}
#[tauri::command]
pub async fn import_playlist(ctrl_handle: State<'_, ControllerHandle>) -> Result<PlaylistPayload, String> {
let file = rfd::AsyncFileDialog::new()
.add_filter("m3u8 Playlist", &["m3u8", "m3u"])
.set_title("Import a Playlist")
.pick_file()
.await
.unwrap();
ctrl_handle.lib_mail.send(LibraryCommand::ImportM3UPlayList(PathBuf::from(file.path()))).await.unwrap();
let LibraryResponse::ImportM3UPlayList(uuid, name) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
println!("Imported Playlist {name}");
Ok(PlaylistPayload {uuid, name})
}
#[derive(Serialize)]
pub struct PlaylistPayload {
uuid: Uuid,
name: String
}
#[tauri::command]
pub async fn get_song(ctrl_handle: State<'_, ControllerHandle>) -> Result<_Song, String> {
ctrl_handle.lib_mail.send(LibraryCommand::Song(Uuid::default())).await.unwrap();
let LibraryResponse::Song(song, _) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
println!("got song {}", &song.tags.get(&Tag::Title).unwrap_or(&String::new()));
Ok(_Song::from(&song))
}

View file

@ -13,6 +13,8 @@ function App() {
const library = useState<JSX.Element[]>([]);
const [queue, setQueue] = useState<JSX.Element[]>([]);
const [playing, setPlaying] = useState(false);
const [playlists, setPlaylists] = useState<JSX.Element[]>([]);
const [viewName, setViewName] = useState("Library");
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
<NowPlaying
@ -72,13 +74,13 @@ function App() {
return (
<main className="container">
<div className="leftSide">
<PlaylistHead />
<MainView lib_ref={ library } />
<PlaylistHead playlists={ playlists } setPlaylists={ setPlaylists } setViewName={ setViewName } setLibrary={ library[1] } />
<MainView lib_ref={ library } viewName={ viewName } />
<PlayBar playing={ playing } setPlaying={ setPlaying } />
</div>
<div className="rightSide">
{ nowPlaying }
<Queue songs={queue} setSongs={ setQueue } />
<Queue songs={ queue } setSongs={ setQueue } />
</div>
</main>
@ -87,47 +89,86 @@ function App() {
export default App;
function getConfig(): any {
invoke('get_config').then( (_config) => {
let config = _config as Config;
if (config.libraries.libraries.length == 0) {
newWindow()
} else {
// console.log("else");
invoke('lib_already_created').then(() => {})
}
})
interface PlaylistHeadProps {
playlists: JSX.Element[]
setPlaylists: React.Dispatch<React.SetStateAction<JSX.Element[]>>,
setViewName: React.Dispatch<React.SetStateAction<string>>,
setLibrary: React.Dispatch<React.SetStateAction<JSX.Element[]>>,
}
function newWindow() {
invoke('new_library_window').then(() => {})
}
function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: PlaylistHeadProps) {
function PlaylistHead() {
let handle_import = () => {
invoke('import_playlist').then((_res) => {
let res = _res as any;
setPlaylists([
...playlists,
<button onClick={ () => {
invoke('get_playlist', { uuid: res.uuid }).then((list) => {
console.log((list as any[]).length);
setLibrary([...(list as any[]).map((song) => {
// console.log(song);
return (
<Song
key={ song.uuid }
location={ song.location }
uuid={ song.uuid }
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
/>
)
})])
})
setViewName( res.name )
} } key={ 'playlist_' + res.uuid }>{ res.name }</button>
])
console.log(res.name);
})
}
return (
<section className="playlistHead">
<button>Library</button>
<button>Playlist 1</button>
<button>Playlist 2</button>
<button>Playlist 3</button>
<button>Playlist 4</button>
<button>Playlist 5</button>
<button>Playlist 6</button>
<button onClick={() => {
setViewName("Library");
invoke('get_library').then((lib) => {
setLibrary([...(lib as any[]).map((song) => {
console.log(song);
return (
<Song
key={ song.uuid }
location={ song.location }
uuid={ song.uuid }
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
/>
)
})])
})
} }>Library</button>
{ playlists }
<button onClick={ handle_import }>Import .m3u8 Playlist</button>
</section>
)
}
interface MainViewProps {
lib_ref: [JSX.Element[], React.Dispatch<React.SetStateAction<JSX.Element[]>>],
viewName: string
}
function MainView({ lib_ref }: MainViewProps) {
function MainView({ lib_ref, viewName }: MainViewProps) {
const [library, setLibrary] = lib_ref;
useEffect(() => {
const unlisten = appWindow.listen<any>("library_loaded", (_) => {
console.log("library_loaded");
invoke('get_library').then((lib) => {
setLibrary([...(lib as any[]).map((song, i) => {
setLibrary([...(lib as any[]).map((song) => {
console.log(song);
return (
@ -146,9 +187,10 @@ function MainView({ lib_ref }: MainViewProps) {
return () => { unlisten.then((f) => f()) }
}, []);
return (
<div className="mainView">
<h1>Library</h1>
<h1>{ viewName }</h1>
<div>{ library }</div>
</div>
)
@ -263,3 +305,19 @@ function QueueSong({ song }: QueueSongProps) {
// </button>
)
}
function getConfig(): any {
invoke('get_config').then( (_config) => {
let config = _config as Config;
if (config.libraries.libraries.length == 0) {
newWindow()
} else {
// console.log("else");
invoke('lib_already_created').then(() => {})
}
})
}
function newWindow() {
invoke('new_library_window').then(() => {})
}