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, pub albums: BTreeMap, pub artists: BTreeMap, pub artwork: BTreeMap, pub genres: BTreeMap, pub keys: BTreeMap, pub labels: BTreeMap, pub nodes: BTreeMap, // Keep playlist entries ordered by entry_index pub entries: BTreeMap>, } 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( // f: &mut File, // page_type: PageType, // start_index: u32, // rows: I, // ) -> std::io::Result<(u32, u32)> // where // I: IntoIterator, // { // 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) -> 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 albums: BTreeMap = BTreeMap::new(); let mut artists: BTreeMap = BTreeMap::new(); let mut artwork: BTreeMap = BTreeMap::new(); let mut genres: BTreeMap = BTreeMap::new(); let mut keys: BTreeMap = BTreeMap::new(); let mut labels: 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::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 }) } }