Files
maple/maple-rs/src/db.rs
T
2025-09-23 21:17:18 +10:00

245 lines
9.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use rekordcrate::pdb::RowGroup;
use rekordcrate::pdb::Artwork;
use rekordcrate::pdb::ArtworkId;
use rekordcrate::pdb::Page;
use rekordcrate::pdb::PageType;
use rekordcrate::pdb::PageIndex;
use std::io::Write;
use std::io::{Seek, SeekFrom};
use binrw::BinWrite;
use rekordcrate::pdb::LabelId;
use rekordcrate::pdb::KeyId;
use rekordcrate::pdb::GenreId;
use rekordcrate::pdb::ArtistId;
use rekordcrate::pdb::AlbumId;
use rekordcrate::pdb::Album;
use rekordcrate::pdb::Artist;
use rekordcrate::pdb::Genre;
use rekordcrate::pdb::Key;
use rekordcrate::pdb::Label;
use rekordcrate::pdb::Table;
use std::fs::File;
use std::path::Path;
use binrw::BinRead;
use std::collections::BTreeMap;
use binrw::Endian;
use rekordcrate::pdb::{
Header, 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<TrackId, Track>,
pub albums: BTreeMap<AlbumId, Album>,
pub artists: BTreeMap<ArtistId, Artist>,
pub artwork: BTreeMap<ArtworkId, Artwork>,
pub genres: BTreeMap<GenreId, Genre>,
pub keys: BTreeMap<KeyId, Key>,
pub labels: BTreeMap<LabelId, Label>,
pub nodes: BTreeMap<PlaylistTreeNodeId, PlaylistTreeNode>,
// Keep playlist entries ordered by entry_index
pub entries: BTreeMap<PlaylistTreeNodeId, Vec<PlaylistEntry>>,
}
const PAGE_SIZE: u16 = 4096;
impl Database {
pub fn export(self, path: &Path) -> Result<(), binrw::Error> {
let pages = Table::from_rows(PageType::Tracks, 1, PAGE_SIZE, self.tracks.values().cloned().map(Row::Track))?;
println!("{:?}", pages.len());
println!("{:?}", pages.last());
Ok(())
}
// pub fn export(self, path: &Path) -> std::io::Result<()> {
// // 1) Reserve header page
// let mut f = File::create(path)?;
// f.write_all(&vec![0u8; PAGE_SIZE as usize])?;
// // 2) Emit tables linearly, collect table metadata
// let mut next_page_index: u32 = 1;
// // ok now we're getting somewhere
// // smash rows into row_groups of 16
// // try to write those into a page until it is full
// // yield pages until all the rows are in
// // set the indices
// // 420 blaze it
// let (tracks_first, tracks_last) =
// Self::write_table(&mut f, PageType::Tracks, next_page_index, self.tracks.values().cloned().map(Row::Track))?;
// next_page_index = tracks_last + 1;
// let (tree_first, tree_last) =
// Self::write_table(&mut f, PageType::PlaylistTree, next_page_index, self.nodes.values().cloned().map(Row::PlaylistTreeNode))?;
// next_page_index = tree_last + 1;
// let plist_entries = self.entries.iter().flat_map(|(_pid, vec)| {
// // ensure contiguous entry_index
// let mut v = vec.clone();
// v.sort_by_key(|e| e.entry_index);
// v.into_iter().map(Row::PlaylistEntry)
// });
// let (entries_first, entries_last) =
// Self::write_table(&mut f, PageType::PlaylistEntries, next_page_index, plist_entries)?;
// next_page_index = entries_last + 1;
// let (art_first, art_last) =
// Self::write_table(&mut f, PageType::Artwork, next_page_index, self.artwork.values().cloned().map(Row::Artwork))?;
// next_page_index = art_last + 1;
// // 3) Build header and back-patch at page 0
// let header = Header {
// // fill other required fields in your crate
// sequence: 1, // or previous+1
// next_unused_page: PageIndex(next_page_index),
// tables: vec![
// Table { page_type: PageType::Tracks, first_page: PageIndex(tracks_first), last_page: PageIndex(tracks_last), empty_candidate: 0, /* … */ },
// Table { page_type: PageType::PlaylistTree, first_page: PageIndex(tree_first), last_page: PageIndex(tree_last), empty_candidate: 0, /* … */ },
// Table { page_type: PageType::PlaylistEntries, first_page: PageIndex(entries_first), last_page: PageIndex(entries_last), empty_candidate: 0, /* … */ },
// Table { page_type: PageType::Artwork, first_page: PageIndex(art_first), last_page: PageIndex(art_last), empty_candidate: 0, /* … */ },
// ],
// ..Default::default()
// };
// // Write header at offset 0 and pad to one page
// f.seek(SeekFrom::Start(0))?;
// header.write(&mut f);
// // pad residual of page 0 if BinWrite didn’t fill it:
// let pos = f.stream_position()?;
// if pos < PAGE_SIZE as u64 {
// f.write_all(&vec![0u8; (PAGE_SIZE as u64 - pos) as usize])?;
// }
// Ok(())
// }
// fn write_table<I>(
// f: &mut File,
// page_type: PageType,
// start_index: u32,
// rows: I,
// ) -> std::io::Result<(u32, u32)>
// where
// I: IntoIterator<Item = Row>,
// {
// let mut page_index = start_index;
// let mut first = 0;
// let mut last = 0;
// // Pack rows into pages. Use your crate’s Page builder to handle row groups.
// let mut packer = PagePacker::new(page_type, PAGE_SIZE);
// for row in rows {
// if !packer.try_push(&row) {
// // flush full page
// let mut page = packer.finish(page_index, /*next_page=*/0);
// // Temporarily set next_page=0; we will fix it after writing the next page.
// Self::write_page(f, &mut page)?;
// if first == 0 { first = page_index; }
// last = page_index;
// page_index += 1;
// // start new page with the row that didn’t fit
// packer = PagePacker::new(page_type, PAGE_SIZE);
// assert!(packer.try_push(&row));
// }
// }
// // flush final page if it has content or if this is an empty table create one empty page
// if packer.is_empty() && first == 0 {
// let mut page = Page::empty(page_type, page_index, /*next_page=*/0, PAGE_SIZE);
// Self::write_page(f, &mut page)?;
// first = page_index;
// last = page_index;
// } else {
// let mut page = packer.finish(page_index, /*next_page=*/0);
// Self::write_page(f, &mut page)?;
// if first == 0 { first = page_index; }
// last = page_index;
// }
// // Fix `next_page` pointers by rewriting page headers if your Page writer requires it.
// // Simpler: during emission, remember the previous page file offset and seek back
// // to patch its `next_page` to `page_index`.
// Ok((first, last))
// }
// fn write_page(f: &mut File, page: &mut Page) -> std::io::Result<()> {
// page.write(f);
// Ok(())
// }
pub fn load_from_pdb(path: impl AsRef<Path>) -> rekordcrate::Result<Self> {
let mut f = std::fs::File::open(path)?;
let header = Header::read(&mut f)?; // uses the type’s endianness
let mut tracks: BTreeMap<TrackId, Track> = BTreeMap::new();
let mut albums: BTreeMap<AlbumId, Album> = BTreeMap::new();
let mut artists: BTreeMap<ArtistId, Artist> = BTreeMap::new();
let mut artwork: BTreeMap<ArtworkId, Artwork> = BTreeMap::new();
let mut genres: BTreeMap<GenreId, Genre> = BTreeMap::new();
let mut keys: BTreeMap<KeyId, Key> = BTreeMap::new();
let mut labels: BTreeMap<LabelId, Label> = BTreeMap::new();
let mut nodes: BTreeMap<PlaylistTreeNodeId, PlaylistTreeNode> = BTreeMap::new();
let mut entries: BTreeMap<PlaylistTreeNodeId, Vec<PlaylistEntry>> = 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::Artist(a) => {
artists.insert(a.id, a.clone());
}
Row::Album(a) => {
albums.insert(a.id, a.clone());
}
Row::Genre(g) => {
genres.insert(g.id, g.clone());
}
Row::Key(k) => {
keys.insert(k.id, k.clone());
}
Row::Label(l) => {
labels.insert(l.id, l.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, albums, artists, artwork, genres, keys, labels, nodes, entries })
}
}