Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Holzhaus committed Oct 11, 2022
1 parent f26c805 commit 8a75859
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 76 deletions.
200 changes: 186 additions & 14 deletions src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,92 @@

//! High-level API for working with Rekordbox device exports.
use crate::{setting, setting::Setting};
use binrw::BinRead;
use std::path::Path;
use crate::{
pdb::{Header, Page, PageType, PlaylistTreeNode, PlaylistTreeNodeId, Row},
setting,
setting::Setting,
};
use binrw::{BinRead, ReadOptions};
use std::collections::HashMap;
use std::path::PathBuf;

/// Represents a Rekordbox device export.
#[derive(Debug, Default, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq)]
pub struct DeviceExport {
path: PathBuf,
pdb: Option<Pdb>,
devsetting: Option<Setting>,
djmmysetting: Option<Setting>,
mysetting: Option<Setting>,
mysetting2: Option<Setting>,
}

impl DeviceExport {
fn load_setting(path: &Path) -> Result<Setting, crate::Error> {
/// Load device export from the given path.
///
/// The path should contain a `PIONEER` directory.
#[must_use]
pub fn new(path: PathBuf) -> Self {
Self {
path,
pdb: None,
devsetting: None,
djmmysetting: None,
mysetting: None,
mysetting2: None,
}
}

fn read_setting_file(path: &PathBuf) -> crate::Result<Setting> {
let mut reader = std::fs::File::open(path)?;
let setting = Setting::read(&mut reader)?;
Ok(setting)
}

/// Load device export from the given path.
///
/// The path should contain a `PIONEER` directory.
pub fn load(&mut self, path: &Path) -> Result<(), crate::Error> {
let path = path.join("PIONEER");
self.devsetting = Some(Self::load_setting(&path.join("DEVSETTING.DAT"))?);
self.djmmysetting = Some(Self::load_setting(&path.join("DJMMYSETTING.DAT"))?);
self.mysetting = Some(Self::load_setting(&path.join("MYSETTING.DAT"))?);
self.mysetting2 = Some(Self::load_setting(&path.join("MYSETTING2.DAT"))?);
/// Load setting files.
pub fn load_settings(&mut self) -> crate::Result<()> {
let path = self.path.join("PIONEER");
self.devsetting = Some(Self::read_setting_file(&path.join("DEVSETTING.DAT"))?);
self.djmmysetting = Some(Self::read_setting_file(&path.join("DJMMYSETTING.DAT"))?);
self.mysetting = Some(Self::read_setting_file(&path.join("MYSETTING.DAT"))?);
self.mysetting2 = Some(Self::read_setting_file(&path.join("MYSETTING2.DAT"))?);

Ok(())
}

fn read_pdb_file(path: &PathBuf) -> crate::Result<Pdb> {
let mut reader = std::fs::File::open(&path)?;
let header = Header::read(&mut reader)?;
let pages = header
.tables
.iter()
.flat_map(|table| {
header
.read_pages(
&mut reader,
&ReadOptions::new(binrw::Endian::NATIVE),
(&table.first_page, &table.last_page),
)
.into_iter()
})
.flatten()
.collect::<Vec<Page>>();

let pdb = Pdb { header, pages };
Ok(pdb)
}

/// Load PDB file.
pub fn load_pdb(&mut self) -> crate::Result<()> {
let path = self
.path
.join("PIONEER")
.join("rekordbox")
.join("export.pdb");
self.pdb = Some(Self::read_pdb_file(&path)?);
Ok(())
}

/// Get the settings from this export.
#[must_use]
pub fn get_settings(&self) -> Settings {
Expand Down Expand Up @@ -70,6 +123,14 @@ impl DeviceExport {

settings
}

/// Get the playlists tree.
pub fn get_playlists(&self) -> crate::Result<Vec<PlaylistNode>> {
match &self.pdb {
Some(pdb) => pdb.get_playlists(),
None => Ok(Vec::new()),
}
}
}

/// Settings object containing for all device settings.
Expand Down Expand Up @@ -235,3 +296,114 @@ impl Settings {
self.waveform_current_position = Some(data.waveform_current_position);
}
}

/// Represent a PDB file.
#[derive(Debug, PartialEq)]
pub struct Pdb {
header: Header,
pages: Vec<Page>,
}

/// Represents either a playlist folder or a playlist.
#[derive(Debug, PartialEq)]
pub enum PlaylistNode {
/// Represents a playlist folder that contains `PlaylistNode`s.
Folder(PlaylistFolder),
/// Represents a playlist.
Playlist(Playlist),
}

/// Represents a playlist folder that contains `PlaylistNode`s.
#[derive(Debug, PartialEq)]
pub struct PlaylistFolder {
/// Name of the playlist folder.
pub name: String,
/// Child nodes of the playlist folder.
pub children: Vec<PlaylistNode>,
}

/// Represents a playlist.
#[derive(Debug, PartialEq, Eq)]
pub struct Playlist {
/// Name of the playlist.
pub name: String,
}

impl Pdb {
/// Create a new `Pdb` object by reading the PDB file at the given path.
pub fn new_from_path(path: &PathBuf) -> crate::Result<Self> {
let mut reader = std::fs::File::open(&path)?;
let header = Header::read(&mut reader)?;
let pages = header
.tables
.iter()
.flat_map(|table| {
header
.read_pages(
&mut reader,
&ReadOptions::new(binrw::Endian::NATIVE),
(&table.first_page, &table.last_page),
)
.into_iter()
})
.flatten()
.collect::<Vec<Page>>();

let pdb = Pdb { header, pages };

Ok(pdb)
}

/// Get playlist tree.
pub fn get_playlists(&self) -> crate::Result<Vec<PlaylistNode>> {
let mut playlists: HashMap<PlaylistTreeNodeId, Vec<PlaylistTreeNode>> = HashMap::new();
self.pages
.iter()
.filter(|page| page.page_type == PageType::PlaylistTree)
.flat_map(|page| page.row_groups.iter())
.flat_map(|row_group| {
row_group
.present_rows()
.map(|row| {
if let Row::PlaylistTreeNode(playlist_tree) = row {
playlist_tree
} else {
unreachable!("encountered non-playlist tree row in playlist table");
}
})
.cloned()
.collect::<Vec<PlaylistTreeNode>>()
.into_iter()
})
.for_each(|row| playlists.entry(row.parent_id).or_default().push(row));

fn get_child_nodes(
playlists: &HashMap<PlaylistTreeNodeId, Vec<PlaylistTreeNode>>,
id: PlaylistTreeNodeId,
) -> impl Iterator<Item = crate::Result<PlaylistNode>> + '_ {
playlists
.get(&id)
.into_iter()
.flat_map(|nodes| nodes.iter())
.map(|node| -> crate::Result<PlaylistNode> {
let child_node = if node.is_folder() {
let folder = PlaylistFolder {
name: node.name.clone().into_string()?,
children: get_child_nodes(playlists, node.id)
.collect::<crate::Result<Vec<PlaylistNode>>>()?,
};
PlaylistNode::Folder(folder)
} else {
let playlist = Playlist {
name: node.name.clone().into_string()?,
};
PlaylistNode::Playlist(playlist)
};
Ok(child_node)
})
}

get_child_nodes(&playlists, PlaylistTreeNodeId(0))
.collect::<crate::Result<Vec<PlaylistNode>>>()
}
}
86 changes: 24 additions & 62 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use binrw::{BinRead, ReadOptions};
use clap::{Parser, Subcommand};
use rekordcrate::anlz::ANLZ;
use rekordcrate::pdb::{Header, PageType, Row};
use rekordcrate::pdb::Header;
use rekordcrate::setting::Setting;
use std::path::{Path, PathBuf};

Expand Down Expand Up @@ -44,7 +44,7 @@ enum Commands {
/// Parse and dump a Pioneer Database (`.PDB`) file.
DumpPDB {
/// File to parse.
#[arg(value_name = "PDB_FILE")]
#[arg(value_name = "EXPORT_PATH")]
path: PathBuf,
},
/// Parse and dump a Pioneer Settings (`*SETTING.DAT`) file.
Expand All @@ -55,75 +55,37 @@ enum Commands {
},
}

fn list_playlists(path: &PathBuf) -> rekordcrate::Result<()> {
use rekordcrate::pdb::{PlaylistTreeNode, PlaylistTreeNodeId};
use std::collections::HashMap;

fn print_children_of(
tree: &HashMap<PlaylistTreeNodeId, Vec<PlaylistTreeNode>>,
id: PlaylistTreeNodeId,
level: usize,
) {
tree.get(&id)
.iter()
.flat_map(|nodes| nodes.iter())
.for_each(|node| {
println!(
"{}{} {}",
" ".repeat(level),
if node.is_folder() { "🗀" } else { "🗎" },
node.name.clone().into_string().unwrap(),
);
print_children_of(tree, node.id, level + 1);
});
}

let mut reader = std::fs::File::open(&path)?;
let header = Header::read(&mut reader)?;

let mut tree: HashMap<PlaylistTreeNodeId, Vec<PlaylistTreeNode>> = HashMap::new();
fn list_playlists(path: &Path) -> rekordcrate::Result<()> {
use rekordcrate::device::PlaylistNode;
use rekordcrate::DeviceExport;

header
.tables
.iter()
.filter(|table| table.page_type == PageType::PlaylistTree)
.flat_map(|table| {
header
.read_pages(
&mut reader,
&ReadOptions::new(binrw::Endian::NATIVE),
(&table.first_page, &table.last_page),
)
.unwrap()
.into_iter()
.flat_map(|page| page.row_groups.into_iter())
.flat_map(|row_group| {
row_group
.present_rows()
.map(|row| {
if let Row::PlaylistTreeNode(playlist_tree) = row {
playlist_tree
} else {
unreachable!("encountered non-playlist tree row in playlist table");
}
})
.cloned()
.collect::<Vec<PlaylistTreeNode>>()
.into_iter()
})
})
.for_each(|row| tree.entry(row.parent_id).or_default().push(row));
let mut export = DeviceExport::new(path.into());
export.load_pdb()?;
let playlists = export.get_playlists()?;

print_children_of(&tree, PlaylistTreeNodeId(0), 0);
fn walk_tree(node: PlaylistNode, level: usize) {
let indent = " ".repeat(level);
match node {
PlaylistNode::Folder(folder) => {
println!("{}🗀 {}", indent, folder.name);
folder
.children
.into_iter()
.for_each(|child| walk_tree(child, level + 1));
}
PlaylistNode::Playlist(playlist) => println!("{}🗎 {}", indent, playlist.name),
};
}
playlists.into_iter().for_each(|node| walk_tree(node, 0));

Ok(())
}

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

let mut export = DeviceExport::default();
export.load(path)?;
let mut export = DeviceExport::new(path.into());
export.load_settings()?;
let settings = export.get_settings();

println!(
Expand Down

0 comments on commit 8a75859

Please sign in to comment.