use chrono::{DateTime, NaiveDateTime};
use quick_xml;
use quick_xml::events::{BytesDecl, Event};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::ffi::OsStr;
use std::io::Write;
use std::path::{Path, PathBuf};
use crate::error::Result;
fn serialize_node_id<S>(id: &String, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
if id == "root" {
return serializer.serialize_str("0");
}
match id.parse::<u64>() {
Ok(number) => serializer.serialize_str(&format!("{:X}", number)),
Err(e) => {
use serde::ser::Error;
Err(S::Error::custom(format!("Failed to parse ID: {}", e)))
}
}
}
fn deserialize_node_id<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
where
D: Deserializer<'de>,
{
let hexid = String::deserialize(deserializer)?;
if hexid == "0" {
return Ok("root".to_string());
}
match u64::from_str_radix(&hexid, 16) {
Ok(id) => Ok(id.to_string()),
Err(e) => {
use serde::de::Error;
Err(D::Error::custom(format!(
"Failed to convert hex to ID: {}",
e
)))
}
}
}
fn serialize_timestamp<S>(
datetime: &NaiveDateTime,
serializer: S,
) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let timestamp_ms = datetime.and_utc().timestamp_millis();
serializer.serialize_i64(timestamp_ms)
}
fn deserialize_timestamp<'de, D>(deserializer: D) -> std::result::Result<NaiveDateTime, D::Error>
where
D: Deserializer<'de>,
{
let timestamp_ms = i64::deserialize(deserializer)?;
let seconds = timestamp_ms / 1000;
let nanos = ((timestamp_ms % 1000) * 1_000_000) as u32;
match DateTime::from_timestamp(seconds, nanos) {
Some(dt) => Ok(dt.naive_utc()),
None => {
use serde::de::Error;
Err(D::Error::custom(format!(
"Invalid timestamp: {}",
timestamp_ms
)))
}
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Product {
#[serde(rename = "@Name")]
pub name: String,
#[serde(rename = "@Version")]
pub version: String,
#[serde(rename = "@Company")]
pub company: String,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Node {
#[serde(
rename = "@Id",
serialize_with = "serialize_node_id",
deserialize_with = "deserialize_node_id"
)]
pub id: String,
#[serde(
rename = "@ParentId",
serialize_with = "serialize_node_id",
deserialize_with = "deserialize_node_id"
)]
pub parent_id: String,
#[serde(rename = "@Attribute")]
pub attribute: i32,
#[serde(
rename = "@Timestamp",
serialize_with = "serialize_timestamp",
deserialize_with = "deserialize_timestamp"
)]
pub timestamp: NaiveDateTime,
#[serde(rename = "@Lib_Type")]
pub lib_type: i32,
#[serde(rename = "@CheckType")]
pub check_type: i32,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Playlists {
#[serde(rename = "NODE")]
pub nodes: Vec<Node>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(rename = "MASTER_PLAYLIST")]
pub struct Document {
#[serde(rename = "@Version")]
pub version: String,
#[serde(rename = "@AutomaticSync")]
pub automatic_sync: i32,
#[serde(rename = "PRODUCT")]
pub product: Product,
#[serde(rename = "PLAYLISTS")]
pub playlists: Playlists,
}
pub struct MasterPlaylistXml {
path: PathBuf,
pub doc: Document,
}
impl MasterPlaylistXml {
pub fn new<P: AsRef<Path> + AsRef<OsStr>>(path: P) -> Self {
let p = Path::new(&path).to_path_buf();
let version = "3.0.0";
let automatic_sync = 0;
let product_name = "rekordbox";
let product_version = "6.0.0";
let product_company = "Pioneer DJ";
let doc = Document {
version: version.to_string(),
automatic_sync,
product: Product {
name: product_name.to_string(),
version: product_version.to_string(),
company: product_company.to_string(),
},
playlists: Playlists { nodes: Vec::new() },
};
MasterPlaylistXml { path: p, doc }
}
pub fn load<P: AsRef<Path> + AsRef<OsStr>>(path: P) -> Self {
let p = Path::new(&path).to_path_buf();
let file = std::fs::File::open(path).expect("File not found");
let reader = std::io::BufReader::new(file);
let doc: Document = quick_xml::de::from_reader(reader).expect("failed to deserialize XML");
MasterPlaylistXml { path: p, doc }
}
pub fn to_string(&self) -> Result<String> {
let buffer: Vec<u8> = Vec::new();
let mut writer = quick_xml::Writer::new(buffer);
writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))?;
let mut decl_string = String::from_utf8(writer.into_inner()).map_err(|e| e.to_string())?;
let mut string = String::new();
let mut ser = quick_xml::se::Serializer::new(&mut string);
ser.indent(' ', 4);
self.doc.serialize(ser)?;
decl_string.push('\n');
string.insert_str(0, &decl_string);
Ok(string)
}
pub fn dump_copy<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml_string = self.to_string()?;
let mut file = std::fs::File::create(path).expect("Failed to create file");
file.write_all(xml_string.as_bytes())?;
Ok(())
}
pub fn dump(&self) -> Result<()> {
self.dump_copy(&self.path)?;
Ok(())
}
pub fn get_playlists(&self) -> Vec<Node> {
self.doc.playlists.nodes.clone()
}
pub fn get_playlist(&self, id: String) -> Option<Node> {
for node in &self.doc.playlists.nodes {
if node.id == id {
return Some(node.clone());
}
}
None
}
pub fn add(&mut self, id: String, parent_id: String, attribute: i32, timestamp: NaiveDateTime) {
let node = Node {
id,
parent_id,
attribute,
timestamp,
lib_type: 0,
check_type: 0,
};
self.doc.playlists.nodes.push(node);
}
pub fn remove(&mut self, id: String) {
self.doc.playlists.nodes.retain(|node| node.id != id);
}
pub fn update(&mut self, id: String, timestamp: NaiveDateTime) {
for node in &mut self.doc.playlists.nodes {
if node.id == id {
node.timestamp = timestamp;
break;
}
}
}
pub fn update_parent(&mut self, id: String, parent_id: String, timestamp: NaiveDateTime) {
for node in &mut self.doc.playlists.nodes {
if node.id == id {
node.parent_id = parent_id;
node.timestamp = timestamp;
break;
}
}
}
}