From 07017c5deff28fc8352c7d65f3b668f17bdfcd72 Mon Sep 17 00:00:00 2001 From: Nathan Rashleigh Date: Wed, 10 Sep 2025 23:03:25 +1000 Subject: [PATCH] ok... --- maple-rs/src/db.rs | 133 ++++++++++++++++++++++++++++++++++++++ maple-rs/src/main.rs | 15 +++++ maple-rs/src/playlists.rs | 6 +- 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 maple-rs/src/db.rs create mode 100644 maple-rs/src/main.rs diff --git a/maple-rs/src/db.rs b/maple-rs/src/db.rs new file mode 100644 index 0000000..113265c --- /dev/null +++ b/maple-rs/src/db.rs @@ -0,0 +1,133 @@ +use std::path::Path; +use binrw::BinRead; +use std::collections::BTreeMap; + +use binrw::Endian; +use rekordcrate::pdb::{ + Header, PageType, Row, + Track, TrackId, + PlaylistTreeNode, PlaylistTreeNodeId, + PlaylistEntry, +}; + +#[derive(Debug)] +pub enum DbError { + UnknownPlaylist, + UnknownTrack, + PositionOutOfBounds, +} + +#[derive(Debug)] +pub struct Database { + pub header: Header, + pub tracks: BTreeMap, + pub nodes: BTreeMap, + // Keep playlist entries ordered by entry_index + pub entries: BTreeMap>, +} + +impl Database { + pub fn load_from_pdb(path: impl AsRef) -> rekordcrate::Result { + let mut f = std::fs::File::open(path)?; + let header = Header::read(&mut f)?; // uses the type’s endianness + + let mut tracks: BTreeMap = BTreeMap::new(); + let mut nodes: BTreeMap = BTreeMap::new(); + let mut entries: BTreeMap> = BTreeMap::new(); + + for table in &header.tables { + let pages = header.read_pages(&mut f, Endian::NATIVE, (&table.first_page, &table.last_page))?; + + for page in pages { + for rg in page.row_groups { + for row in rg.present_rows() { + match row { + Row::Track(t) => { + tracks.insert(t.id, t.clone()); + } + Row::PlaylistTreeNode(n) => { + nodes.insert(n.id, n.clone()); + } + Row::PlaylistEntry(e) => { + entries.entry(e.playlist_id).or_default().push(e.clone()); + } + _ => {} + } + } + } + } + } + + // Ensure deterministic ordering within each playlist + for v in entries.values_mut() { + v.sort_by_key(|e| e.entry_index); // needs `pub entry_index` + } + + Ok(Self { header, tracks, nodes, entries }) + } + /// Export to low-level tables. + pub fn into_tables( + self, + ) -> ( + Header, + Vec, + Vec, + Vec<(PlaylistTreeNodeId, PlaylistEntry)>, + ) { + let nodes = self.nodes.into_values().collect(); + let tracks = self.tracks.into_values().collect(); + let mut flat_entries = Vec::new(); + for (pid, vs) in self.entries { + for e in vs { + flat_entries.push((pid, e)); + } + } + (self.header, tracks, nodes, flat_entries) + } + + /// Add a ready-made entry (you build it). + pub fn add_entry( + &mut self, + playlist_id: PlaylistTreeNodeId, + entry: PlaylistEntry, + position: Option, + ) -> Result<(), DbError> { + if !self.nodes.contains_key(&playlist_id) { + return Err(DbError::UnknownPlaylist); + } + let vec = self.entries.entry(playlist_id).or_default(); + match position { + Some(i) if i <= vec.len() => vec.insert(i, entry), + Some(_) => return Err(DbError::PositionOutOfBounds), + None => vec.push(entry), + } + Ok(()) + } + + /// Convenience: you pass a track id and a constructor for `PlaylistEntry`. + pub fn add_track_to_playlist_with( + &mut self, + playlist_id: PlaylistTreeNodeId, + track_id: TrackId, + position: Option, + make_entry: E, + ) -> Result<(), DbError> + where + E: FnOnce(TrackId) -> PlaylistEntry, + { + if !self.nodes.contains_key(&playlist_id) { + return Err(DbError::UnknownPlaylist); + } + if !self.tracks.contains_key(&track_id) { + return Err(DbError::UnknownTrack); + } + let entry = make_entry(track_id); + self.add_entry(playlist_id, entry, position) + } + + // fn alloc_node_id(&mut self) -> u32 { + // let id = self.next_node_id; + // self.next_node_id = self.next_node_id.saturating_add(1); + // id + // } +} diff --git a/maple-rs/src/main.rs b/maple-rs/src/main.rs new file mode 100644 index 0000000..5555d7c --- /dev/null +++ b/maple-rs/src/main.rs @@ -0,0 +1,15 @@ +mod db; + +use db::Database; + +// Example +fn main() -> rekordcrate::Result<()> { + let db = Database::load_from_pdb("ntr.pdb")?; + println!("tracks: {}, playlists: {}", db.tracks.len(), db.nodes.len()); + + for n in db.nodes { + println!("{:?}", n); + } + + Ok(()) +} \ No newline at end of file diff --git a/maple-rs/src/playlists.rs b/maple-rs/src/playlists.rs index af2019c..3666992 100644 --- a/maple-rs/src/playlists.rs +++ b/maple-rs/src/playlists.rs @@ -1,8 +1,12 @@ +mod db; + use binrw::BinRead; use std::path::PathBuf; use rekordcrate::pdb::{Header, Row, PageType, PlaylistTreeNode, PlaylistTreeNodeId}; use std::collections::HashMap; +use db::Database; + fn list_playlists(path: &PathBuf) -> rekordcrate::Result<()> { fn print_children_of( @@ -67,7 +71,7 @@ fn list_playlists(path: &PathBuf) -> rekordcrate::Result<()> { fn main() -> Result<(), Box> { - let p = PathBuf::from("../data/PIONEER/rekordbox/export.pdb"); + let p = PathBuf::from("./ntr.pdb"); list_playlists(&p)?;