mut rgs
This commit is contained in:
1 parent
690f92f565
commit
f804c327de
3 files changed
+10
-288
No files matched your search
@@ -1,283 +0,0 @@
|
|||||||
use rekordcrate::pdb::Page;
|
|
||||||
use rekordcrate::util::ColorIndex;
|
|
||||||
use rekordcrate::pdb::Color;
|
|
||||||
use rekordcrate::util::RekordcrateError;
|
|
||||||
use rekordcrate::pdb::Artwork;
|
|
||||||
use rekordcrate::pdb::ArtworkId;
|
|
||||||
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 genres: BTreeMap<GenreId, Genre>,
|
|
||||||
pub artists: BTreeMap<ArtistId, Artist>,
|
|
||||||
pub albums: BTreeMap<AlbumId, Album>,
|
|
||||||
pub labels: BTreeMap<LabelId, Label>,
|
|
||||||
pub keys: BTreeMap<KeyId, Key>,
|
|
||||||
pub colors: BTreeMap<ColorIndex, Color>,
|
|
||||||
pub playlist_tree: BTreeMap<PlaylistTreeNodeId, PlaylistTreeNode>,
|
|
||||||
// Keep playlist entries ordered by entry_index
|
|
||||||
pub playlist_entries: BTreeMap<PlaylistTreeNodeId, Vec<PlaylistEntry>>,
|
|
||||||
pub artwork: BTreeMap<ArtworkId, Artwork>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const PAGE_SIZE: u16 = 4096;
|
|
||||||
|
|
||||||
impl Database {
|
|
||||||
// pub fn export(self, path: &Path) -> Result<(), RekordcrateError> {
|
|
||||||
// let table = Table::from_rows(PageType::Tracks, 1, PAGE_SIZE, self.tracks.values().cloned().map(Row::Track))?;
|
|
||||||
// println!("{:?}", table);
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Iterator over (PageType, rows) in file order.
|
|
||||||
fn table_rows(&self) -> Vec<(PageType, Vec<Row>)> {
|
|
||||||
let mut playlist_rows: Vec<Row> = Vec::new();
|
|
||||||
for (_pid, mut es) in &self.playlist_entries {
|
|
||||||
let mut v = es.clone();
|
|
||||||
v.sort_by_key(|e| e.entry_index);
|
|
||||||
playlist_rows.extend(v.into_iter().map(Row::PlaylistEntry));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![
|
|
||||||
// (PageType::Tracks, self.tracks.values().cloned().map(Row::Track).collect()),
|
|
||||||
// (PageType::Genres, self.genres.values().cloned().map(Row::Genre).collect()),
|
|
||||||
// (PageType::Artists, self.artists.values().cloned().map(Row::Artist).collect()),
|
|
||||||
// (PageType::Albums, self.albums.values().cloned().map(Row::Album).collect()),
|
|
||||||
// (PageType::Labels, self.labels.values().cloned().map(Row::Label).collect()),
|
|
||||||
// (PageType::Keys, self.keys.values().cloned().map(Row::Key).collect()),
|
|
||||||
// (PageType::Colors, self.colors.values().cloned().map(Row::Color).collect()),
|
|
||||||
// (PageType::PlaylistTree, self.playlist_tree.values().cloned().map(Row::PlaylistTreeNode).collect()),
|
|
||||||
// (PageType::PlaylistEntries, playlist_rows),
|
|
||||||
// (PageType::HistoryPlaylists, Vec::new()),
|
|
||||||
// (PageType::HistoryEntries, Vec::new()),
|
|
||||||
// (PageType::Artwork, self.artwork.values().cloned().map(Row::Artwork).collect()),
|
|
||||||
// (PageType::Columns, Vec::new()),
|
|
||||||
// (PageType::History, Vec::new()),
|
|
||||||
(PageType::Tracks, vec![]),
|
|
||||||
(PageType::Genres, vec![]),
|
|
||||||
(PageType::Artists, vec![]),
|
|
||||||
(PageType::Albums, vec![]),
|
|
||||||
(PageType::Labels, vec![]),
|
|
||||||
(PageType::Keys, vec![]),
|
|
||||||
(PageType::Colors, vec![]),
|
|
||||||
(PageType::PlaylistTree, vec![]),
|
|
||||||
(PageType::PlaylistEntries, vec![]),
|
|
||||||
(PageType::HistoryPlaylists, Vec::new()),
|
|
||||||
(PageType::HistoryEntries, Vec::new()),
|
|
||||||
(PageType::Artwork, vec![]),
|
|
||||||
(PageType::Columns, Vec::new()),
|
|
||||||
(PageType::History, Vec::new()),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write all tables, sequential pages, return header.tables and next_unused_page.
|
|
||||||
fn get_tables_and_pages(
|
|
||||||
&self,
|
|
||||||
start_index: u32,
|
|
||||||
page_size: u16,
|
|
||||||
) -> Result<(Vec<Table>, Vec<Page>, u32), RekordcrateError> {
|
|
||||||
let mut next = start_index;
|
|
||||||
let mut tables = Vec::new();
|
|
||||||
let mut pages = Vec::new();
|
|
||||||
|
|
||||||
for (pt, rows) in self.table_rows().into_iter() {
|
|
||||||
let (table, mut row_pages, np) = Table::from_rows(pt, next, page_size, rows)?;
|
|
||||||
pages.append(&mut row_pages);
|
|
||||||
tables.push(table);
|
|
||||||
next = np;
|
|
||||||
}
|
|
||||||
Ok((tables, pages, next))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn export(self, path: &Path) -> Result<(), RekordcrateError> {
|
|
||||||
let mut f = File::create(path)?;
|
|
||||||
// reserve the file header space
|
|
||||||
// tables begin at 28
|
|
||||||
f.write_all(&vec![0u8; 28 as usize])?;
|
|
||||||
|
|
||||||
let (tables, pages, next_page) = self.get_tables_and_pages(1, PAGE_SIZE)?;
|
|
||||||
|
|
||||||
for table in tables.clone() {
|
|
||||||
println!("Writing table={:?} at {:?}",table, f.stream_position());
|
|
||||||
table.write_le(&mut f)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(4096)).unwrap();
|
|
||||||
|
|
||||||
for page in pages {
|
|
||||||
println!("Writing page={:?} at {:?}", page, f.stream_position());
|
|
||||||
page.write_le_args(&mut f, (PAGE_SIZE.into(),)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let header = Header {
|
|
||||||
sequence: 0,
|
|
||||||
next_unused_page: PageIndex(next_page),
|
|
||||||
tables,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(0))?;
|
|
||||||
println!("Writing header at {:?}", f.stream_position());
|
|
||||||
header.write_le(&mut f).unwrap();
|
|
||||||
|
|
||||||
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 colors: BTreeMap<ColorIndex, Color> = 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 playlist_tree: BTreeMap<PlaylistTreeNodeId, PlaylistTreeNode> = BTreeMap::new();
|
|
||||||
let mut playlist_entries: BTreeMap<PlaylistTreeNodeId, Vec<PlaylistEntry>> = BTreeMap::new();
|
|
||||||
|
|
||||||
for table in &header.tables {
|
|
||||||
let pages = header.read_pages(&mut f, Endian::Little, (&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::Color(c) => {
|
|
||||||
colors.insert(c.color.clone(), c.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) => {
|
|
||||||
playlist_tree.insert(n.id, n.clone());
|
|
||||||
}
|
|
||||||
Row::PlaylistEntry(e) => {
|
|
||||||
playlist_entries.entry(e.playlist_id).or_default().push(e.clone());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure deterministic ordering within each playlist
|
|
||||||
for v in playlist_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, playlist_tree, playlist_entries, colors })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
mod db;
|
|
||||||
|
|
||||||
extern crate walkdir;
|
extern crate walkdir;
|
||||||
|
|
||||||
use rekordcrate::util::RekordcrateError;
|
use rekordcrate::util::RekordcrateError;
|
||||||
|
use rekordcrate::pdb::Database;
|
||||||
|
use std::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use db::Database;
|
|
||||||
|
const PAGE_SIZE: u32 = 4096;
|
||||||
|
|
||||||
// Example
|
// Example
|
||||||
fn main() -> Result<(), RekordcrateError> {
|
fn main() -> Result<(), RekordcrateError> {
|
||||||
@@ -13,7 +14,8 @@ fn main() -> Result<(), RekordcrateError> {
|
|||||||
println!("{:?}", db.colors);
|
println!("{:?}", db.colors);
|
||||||
|
|
||||||
let output = Path::new("./data/output.pdb");
|
let output = Path::new("./data/output.pdb");
|
||||||
db.export(&output).unwrap();
|
let mut f = File::create(output)?;
|
||||||
|
db.export(PAGE_SIZE, &mut f)?;
|
||||||
|
|
||||||
// let db = Database::load_from_pdb("./data/output.pdb").unwrap();
|
// let db = Database::load_from_pdb("./data/output.pdb").unwrap();
|
||||||
// println!("{:?}", db);
|
// println!("{:?}", db);
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
"path": ".",
|
"path": ".",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/home/ntr/code/rekordcrate"
|
"path": "/home/ntr/code/rekordcrate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/Users/nathanrashleigh/code/code/rekordcrate"
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"settings":
|
"settings":
|
||||||
|
|||||||
Reference in new issue
Block a user