Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
690f92f565 | ||
|
|
076c60658d | ||
|
|
13ee0dcf01 | ||
|
|
d35b51d4c3 |
No files matched your search
+119
-80
@@ -1,7 +1,9 @@
|
||||
use rekordcrate::pdb::RowGroup;
|
||||
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::Page;
|
||||
use rekordcrate::pdb::PageType;
|
||||
use rekordcrate::pdb::PageIndex;
|
||||
use std::io::Write;
|
||||
@@ -42,88 +44,121 @@ pub enum DbError {
|
||||
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 artists: BTreeMap<ArtistId, Artist>,
|
||||
pub albums: BTreeMap<AlbumId, Album>,
|
||||
pub labels: BTreeMap<LabelId, Label>,
|
||||
pub nodes: BTreeMap<PlaylistTreeNodeId, PlaylistTreeNode>,
|
||||
pub keys: BTreeMap<KeyId, Key>,
|
||||
pub colors: BTreeMap<ColorIndex, Color>,
|
||||
pub playlist_tree: BTreeMap<PlaylistTreeNodeId, PlaylistTreeNode>,
|
||||
// Keep playlist entries ordered by entry_index
|
||||
pub entries: BTreeMap<PlaylistTreeNodeId, Vec<PlaylistEntry>>,
|
||||
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<(), 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])?;
|
||||
// }
|
||||
// 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,
|
||||
@@ -189,15 +224,16 @@ impl Database {
|
||||
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 nodes: BTreeMap<PlaylistTreeNodeId, PlaylistTreeNode> = BTreeMap::new();
|
||||
let mut entries: BTreeMap<PlaylistTreeNodeId, Vec<PlaylistEntry>> = 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::NATIVE, (&table.first_page, &table.last_page))?;
|
||||
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 {
|
||||
@@ -212,6 +248,9 @@ impl Database {
|
||||
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());
|
||||
}
|
||||
@@ -222,10 +261,10 @@ impl Database {
|
||||
labels.insert(l.id, l.clone());
|
||||
}
|
||||
Row::PlaylistTreeNode(n) => {
|
||||
nodes.insert(n.id, n.clone());
|
||||
playlist_tree.insert(n.id, n.clone());
|
||||
}
|
||||
Row::PlaylistEntry(e) => {
|
||||
entries.entry(e.playlist_id).or_default().push(e.clone());
|
||||
playlist_entries.entry(e.playlist_id).or_default().push(e.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -235,10 +274,10 @@ impl Database {
|
||||
}
|
||||
|
||||
// Ensure deterministic ordering within each playlist
|
||||
for v in entries.values_mut() {
|
||||
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, nodes, entries })
|
||||
Ok(Self { header, tracks, albums, artists, artwork, genres, keys, labels, playlist_tree, playlist_entries, colors })
|
||||
}
|
||||
}
|
||||
+11
-20
@@ -2,30 +2,21 @@ mod db;
|
||||
|
||||
extern crate walkdir;
|
||||
|
||||
use rekordcrate::pdb::Page;
|
||||
use rekordcrate::pdb::PageIndex;
|
||||
use rekordcrate::pdb::Row::PlaylistEntry;
|
||||
use rekordcrate::pdb::string::DeviceSQLString;
|
||||
use rekordcrate::pdb::RowGroup;
|
||||
use rekordcrate::pdb::TrackId;
|
||||
use rekordcrate::pdb::PageType;
|
||||
use rekordcrate::pdb::Table;
|
||||
use rekordcrate::pdb::{Header, PlaylistTreeNode, PlaylistTreeNodeId};
|
||||
use walkdir::WalkDir;
|
||||
use std::env;
|
||||
use std::fs::create_dir;
|
||||
use std::fs::create_dir_all;
|
||||
use std::{fs::exists, path::Path};
|
||||
use rekordcrate::util::RekordcrateError;
|
||||
use std::path::Path;
|
||||
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());
|
||||
println!("tables: {:?}", db.header);
|
||||
println!("tables: {:?}", db.header.tables);
|
||||
fn main() -> Result<(), RekordcrateError> {
|
||||
let db = Database::load_from_pdb("ntr.pdb").unwrap();
|
||||
|
||||
db.export(&Path::new("./data/output.pdb"))?;
|
||||
println!("{:?}", db.colors);
|
||||
|
||||
let output = Path::new("./data/output.pdb");
|
||||
db.export(&output).unwrap();
|
||||
|
||||
// let db = Database::load_from_pdb("./data/output.pdb").unwrap();
|
||||
// println!("{:?}", db);
|
||||
|
||||
// for n in db.nodes {
|
||||
// println!("{:?}", n);
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
{
|
||||
"path": ".",
|
||||
},
|
||||
{
|
||||
"path": "h:ome\\ntr\\code\\rekordcrate",
|
||||
},
|
||||
{
|
||||
"path": "C:\\Users\\ntr\\code\\rekordcrate",
|
||||
},
|
||||
{
|
||||
"path": "/home/ntr/code/rekordcrate"
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user