Skip to content

Commit 8a75859

Browse files
committed
WIP
1 parent f26c805 commit 8a75859

2 files changed

Lines changed: 210 additions & 76 deletions

File tree

src/device.rs

Lines changed: 186 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,92 @@
88

99
//! High-level API for working with Rekordbox device exports.
1010
11-
use crate::{setting, setting::Setting};
12-
use binrw::BinRead;
13-
use std::path::Path;
11+
use crate::{
12+
pdb::{Header, Page, PageType, PlaylistTreeNode, PlaylistTreeNodeId, Row},
13+
setting,
14+
setting::Setting,
15+
};
16+
use binrw::{BinRead, ReadOptions};
17+
use std::collections::HashMap;
18+
use std::path::PathBuf;
1419

1520
/// Represents a Rekordbox device export.
16-
#[derive(Debug, Default, PartialEq, Eq, Clone)]
21+
#[derive(Debug, PartialEq)]
1722
pub struct DeviceExport {
23+
path: PathBuf,
24+
pdb: Option<Pdb>,
1825
devsetting: Option<Setting>,
1926
djmmysetting: Option<Setting>,
2027
mysetting: Option<Setting>,
2128
mysetting2: Option<Setting>,
2229
}
2330

2431
impl DeviceExport {
25-
fn load_setting(path: &Path) -> Result<Setting, crate::Error> {
32+
/// Load device export from the given path.
33+
///
34+
/// The path should contain a `PIONEER` directory.
35+
#[must_use]
36+
pub fn new(path: PathBuf) -> Self {
37+
Self {
38+
path,
39+
pdb: None,
40+
devsetting: None,
41+
djmmysetting: None,
42+
mysetting: None,
43+
mysetting2: None,
44+
}
45+
}
46+
47+
fn read_setting_file(path: &PathBuf) -> crate::Result<Setting> {
2648
let mut reader = std::fs::File::open(path)?;
2749
let setting = Setting::read(&mut reader)?;
2850
Ok(setting)
2951
}
3052

31-
/// Load device export from the given path.
32-
///
33-
/// The path should contain a `PIONEER` directory.
34-
pub fn load(&mut self, path: &Path) -> Result<(), crate::Error> {
35-
let path = path.join("PIONEER");
36-
self.devsetting = Some(Self::load_setting(&path.join("DEVSETTING.DAT"))?);
37-
self.djmmysetting = Some(Self::load_setting(&path.join("DJMMYSETTING.DAT"))?);
38-
self.mysetting = Some(Self::load_setting(&path.join("MYSETTING.DAT"))?);
39-
self.mysetting2 = Some(Self::load_setting(&path.join("MYSETTING2.DAT"))?);
53+
/// Load setting files.
54+
pub fn load_settings(&mut self) -> crate::Result<()> {
55+
let path = self.path.join("PIONEER");
56+
self.devsetting = Some(Self::read_setting_file(&path.join("DEVSETTING.DAT"))?);
57+
self.djmmysetting = Some(Self::read_setting_file(&path.join("DJMMYSETTING.DAT"))?);
58+
self.mysetting = Some(Self::read_setting_file(&path.join("MYSETTING.DAT"))?);
59+
self.mysetting2 = Some(Self::read_setting_file(&path.join("MYSETTING2.DAT"))?);
4060

4161
Ok(())
4262
}
4363

64+
fn read_pdb_file(path: &PathBuf) -> crate::Result<Pdb> {
65+
let mut reader = std::fs::File::open(&path)?;
66+
let header = Header::read(&mut reader)?;
67+
let pages = header
68+
.tables
69+
.iter()
70+
.flat_map(|table| {
71+
header
72+
.read_pages(
73+
&mut reader,
74+
&ReadOptions::new(binrw::Endian::NATIVE),
75+
(&table.first_page, &table.last_page),
76+
)
77+
.into_iter()
78+
})
79+
.flatten()
80+
.collect::<Vec<Page>>();
81+
82+
let pdb = Pdb { header, pages };
83+
Ok(pdb)
84+
}
85+
86+
/// Load PDB file.
87+
pub fn load_pdb(&mut self) -> crate::Result<()> {
88+
let path = self
89+
.path
90+
.join("PIONEER")
91+
.join("rekordbox")
92+
.join("export.pdb");
93+
self.pdb = Some(Self::read_pdb_file(&path)?);
94+
Ok(())
95+
}
96+
4497
/// Get the settings from this export.
4598
#[must_use]
4699
pub fn get_settings(&self) -> Settings {
@@ -70,6 +123,14 @@ impl DeviceExport {
70123

71124
settings
72125
}
126+
127+
/// Get the playlists tree.
128+
pub fn get_playlists(&self) -> crate::Result<Vec<PlaylistNode>> {
129+
match &self.pdb {
130+
Some(pdb) => pdb.get_playlists(),
131+
None => Ok(Vec::new()),
132+
}
133+
}
73134
}
74135

75136
/// Settings object containing for all device settings.
@@ -235,3 +296,114 @@ impl Settings {
235296
self.waveform_current_position = Some(data.waveform_current_position);
236297
}
237298
}
299+
300+
/// Represent a PDB file.
301+
#[derive(Debug, PartialEq)]
302+
pub struct Pdb {
303+
header: Header,
304+
pages: Vec<Page>,
305+
}
306+
307+
/// Represents either a playlist folder or a playlist.
308+
#[derive(Debug, PartialEq)]
309+
pub enum PlaylistNode {
310+
/// Represents a playlist folder that contains `PlaylistNode`s.
311+
Folder(PlaylistFolder),
312+
/// Represents a playlist.
313+
Playlist(Playlist),
314+
}
315+
316+
/// Represents a playlist folder that contains `PlaylistNode`s.
317+
#[derive(Debug, PartialEq)]
318+
pub struct PlaylistFolder {
319+
/// Name of the playlist folder.
320+
pub name: String,
321+
/// Child nodes of the playlist folder.
322+
pub children: Vec<PlaylistNode>,
323+
}
324+
325+
/// Represents a playlist.
326+
#[derive(Debug, PartialEq, Eq)]
327+
pub struct Playlist {
328+
/// Name of the playlist.
329+
pub name: String,
330+
}
331+
332+
impl Pdb {
333+
/// Create a new `Pdb` object by reading the PDB file at the given path.
334+
pub fn new_from_path(path: &PathBuf) -> crate::Result<Self> {
335+
let mut reader = std::fs::File::open(&path)?;
336+
let header = Header::read(&mut reader)?;
337+
let pages = header
338+
.tables
339+
.iter()
340+
.flat_map(|table| {
341+
header
342+
.read_pages(
343+
&mut reader,
344+
&ReadOptions::new(binrw::Endian::NATIVE),
345+
(&table.first_page, &table.last_page),
346+
)
347+
.into_iter()
348+
})
349+
.flatten()
350+
.collect::<Vec<Page>>();
351+
352+
let pdb = Pdb { header, pages };
353+
354+
Ok(pdb)
355+
}
356+
357+
/// Get playlist tree.
358+
pub fn get_playlists(&self) -> crate::Result<Vec<PlaylistNode>> {
359+
let mut playlists: HashMap<PlaylistTreeNodeId, Vec<PlaylistTreeNode>> = HashMap::new();
360+
self.pages
361+
.iter()
362+
.filter(|page| page.page_type == PageType::PlaylistTree)
363+
.flat_map(|page| page.row_groups.iter())
364+
.flat_map(|row_group| {
365+
row_group
366+
.present_rows()
367+
.map(|row| {
368+
if let Row::PlaylistTreeNode(playlist_tree) = row {
369+
playlist_tree
370+
} else {
371+
unreachable!("encountered non-playlist tree row in playlist table");
372+
}
373+
})
374+
.cloned()
375+
.collect::<Vec<PlaylistTreeNode>>()
376+
.into_iter()
377+
})
378+
.for_each(|row| playlists.entry(row.parent_id).or_default().push(row));
379+
380+
fn get_child_nodes(
381+
playlists: &HashMap<PlaylistTreeNodeId, Vec<PlaylistTreeNode>>,
382+
id: PlaylistTreeNodeId,
383+
) -> impl Iterator<Item = crate::Result<PlaylistNode>> + '_ {
384+
playlists
385+
.get(&id)
386+
.into_iter()
387+
.flat_map(|nodes| nodes.iter())
388+
.map(|node| -> crate::Result<PlaylistNode> {
389+
let child_node = if node.is_folder() {
390+
let folder = PlaylistFolder {
391+
name: node.name.clone().into_string()?,
392+
children: get_child_nodes(playlists, node.id)
393+
.collect::<crate::Result<Vec<PlaylistNode>>>()?,
394+
};
395+
PlaylistNode::Folder(folder)
396+
} else {
397+
let playlist = Playlist {
398+
name: node.name.clone().into_string()?,
399+
};
400+
PlaylistNode::Playlist(playlist)
401+
};
402+
Ok(child_node)
403+
})
404+
}
405+
406+
get_child_nodes(&playlists, PlaylistTreeNodeId(0))
407+
.collect::<crate::Result<Vec<PlaylistNode>>>()
408+
}
409+
}

src/main.rs

Lines changed: 24 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use binrw::{BinRead, ReadOptions};
1010
use clap::{Parser, Subcommand};
1111
use rekordcrate::anlz::ANLZ;
12-
use rekordcrate::pdb::{Header, PageType, Row};
12+
use rekordcrate::pdb::Header;
1313
use rekordcrate::setting::Setting;
1414
use std::path::{Path, PathBuf};
1515

@@ -44,7 +44,7 @@ enum Commands {
4444
/// Parse and dump a Pioneer Database (`.PDB`) file.
4545
DumpPDB {
4646
/// File to parse.
47-
#[arg(value_name = "PDB_FILE")]
47+
#[arg(value_name = "EXPORT_PATH")]
4848
path: PathBuf,
4949
},
5050
/// Parse and dump a Pioneer Settings (`*SETTING.DAT`) file.
@@ -55,75 +55,37 @@ enum Commands {
5555
},
5656
}
5757

58-
fn list_playlists(path: &PathBuf) -> rekordcrate::Result<()> {
59-
use rekordcrate::pdb::{PlaylistTreeNode, PlaylistTreeNodeId};
60-
use std::collections::HashMap;
61-
62-
fn print_children_of(
63-
tree: &HashMap<PlaylistTreeNodeId, Vec<PlaylistTreeNode>>,
64-
id: PlaylistTreeNodeId,
65-
level: usize,
66-
) {
67-
tree.get(&id)
68-
.iter()
69-
.flat_map(|nodes| nodes.iter())
70-
.for_each(|node| {
71-
println!(
72-
"{}{} {}",
73-
" ".repeat(level),
74-
if node.is_folder() { "🗀" } else { "🗎" },
75-
node.name.clone().into_string().unwrap(),
76-
);
77-
print_children_of(tree, node.id, level + 1);
78-
});
79-
}
80-
81-
let mut reader = std::fs::File::open(&path)?;
82-
let header = Header::read(&mut reader)?;
83-
84-
let mut tree: HashMap<PlaylistTreeNodeId, Vec<PlaylistTreeNode>> = HashMap::new();
58+
fn list_playlists(path: &Path) -> rekordcrate::Result<()> {
59+
use rekordcrate::device::PlaylistNode;
60+
use rekordcrate::DeviceExport;
8561

86-
header
87-
.tables
88-
.iter()
89-
.filter(|table| table.page_type == PageType::PlaylistTree)
90-
.flat_map(|table| {
91-
header
92-
.read_pages(
93-
&mut reader,
94-
&ReadOptions::new(binrw::Endian::NATIVE),
95-
(&table.first_page, &table.last_page),
96-
)
97-
.unwrap()
98-
.into_iter()
99-
.flat_map(|page| page.row_groups.into_iter())
100-
.flat_map(|row_group| {
101-
row_group
102-
.present_rows()
103-
.map(|row| {
104-
if let Row::PlaylistTreeNode(playlist_tree) = row {
105-
playlist_tree
106-
} else {
107-
unreachable!("encountered non-playlist tree row in playlist table");
108-
}
109-
})
110-
.cloned()
111-
.collect::<Vec<PlaylistTreeNode>>()
112-
.into_iter()
113-
})
114-
})
115-
.for_each(|row| tree.entry(row.parent_id).or_default().push(row));
62+
let mut export = DeviceExport::new(path.into());
63+
export.load_pdb()?;
64+
let playlists = export.get_playlists()?;
11665

117-
print_children_of(&tree, PlaylistTreeNodeId(0), 0);
66+
fn walk_tree(node: PlaylistNode, level: usize) {
67+
let indent = " ".repeat(level);
68+
match node {
69+
PlaylistNode::Folder(folder) => {
70+
println!("{}🗀 {}", indent, folder.name);
71+
folder
72+
.children
73+
.into_iter()
74+
.for_each(|child| walk_tree(child, level + 1));
75+
}
76+
PlaylistNode::Playlist(playlist) => println!("{}🗎 {}", indent, playlist.name),
77+
};
78+
}
79+
playlists.into_iter().for_each(|node| walk_tree(node, 0));
11880

11981
Ok(())
12082
}
12183

12284
fn list_settings(path: &Path) -> rekordcrate::Result<()> {
12385
use rekordcrate::DeviceExport;
12486

125-
let mut export = DeviceExport::default();
126-
export.load(path)?;
87+
let mut export = DeviceExport::new(path.into());
88+
export.load_settings()?;
12789
let settings = export.get_settings();
12890

12991
println!(

0 commit comments

Comments
 (0)