use chrono::NaiveDate;
use lazy_regex::regex_replace_all;
use quick_xml;
use quick_xml::events::{BytesDecl, Event};
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
use std::ffi::OsStr;
use std::fmt::{Debug, Display};
use std::io::Write;
use std::path::{Path, PathBuf};
use crate::error::{Error, Result};
fn octet_to_hex(arg: u8) -> String {
let r = format!("{:x}", arg);
((if r.len() == 1 { "0" } else { "" }).to_owned() + &r)
.to_uppercase()
.to_owned()
}
fn encode_uri(s: impl AsRef<str>) -> String {
let mut s = s.as_ref().to_string();
s.insert_str(0, "file://localhost/");
regex_replace_all!(r"[^A-Za-z0-9_\-\.:/\\]", s.as_ref(), |seq: &str| {
let mut r = String::new();
for ch in seq.to_owned().bytes() {
r.push('%');
r.push_str(octet_to_hex(ch).as_ref());
}
r.clone()
})
.into_owned()
}
fn decode_uri(s: impl AsRef<str>) -> String {
let p = regex_replace_all!(r"(%[A-Fa-f0-9]{2})+", s.as_ref(), |seq: &str, _| {
let mut r = Vec::<u8>::new();
let inp: Vec<u8> = seq.to_owned().bytes().collect();
let mut i: usize = 0;
while i != inp.len() {
r.push(
u8::from_str_radix(
String::from_utf8_lossy(&[inp[i + 1], inp[i + 2]]).as_ref(),
16,
)
.unwrap_or(0),
);
i += 3;
}
String::from_utf8_lossy(r.as_ref()).into_owned().to_owned()
})
.into_owned();
p.strip_prefix("file://localhost/")
.unwrap_or(s.as_ref())
.to_string()
}
fn serialize_location<S>(value: &String, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let value_encoded = encode_uri(value);
value_encoded.serialize(serializer)
}
fn deserialize_location<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
where
D: Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
let val = decode_uri(&s);
Ok(val)
}
#[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, Clone, PartialEq, Serialize, Deserialize)]
pub struct Tempo {
#[serde(rename = "@Inizio")]
pub inizio: f64,
#[serde(rename = "@Bpm")]
pub bpm: f64,
#[serde(rename = "@Metro")]
pub metro: String,
#[serde(rename = "@Battito")]
pub battito: u16,
}
impl Tempo {
pub fn new(inizio: f64, bpm: f64, metro: &str, battito: u16) -> Self {
Self {
inizio,
bpm,
metro: metro.to_string(),
battito,
}
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct PositionMark {
#[serde(rename = "@Name")]
pub name: String,
#[serde(rename = "@Type")]
pub mark_type: u16,
#[serde(rename = "@Start")]
pub start: f64,
#[serde(rename = "@End")]
#[serde(skip_serializing_if = "Option::is_none")]
pub end: Option<f64>,
#[serde(rename = "@Num")]
pub num: i32,
}
impl PositionMark {
pub fn new(name: &str, mark_type: u16, num: i32, start: f64, end: Option<f64>) -> Self {
Self {
name: name.to_string(),
mark_type,
start,
end,
num,
}
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Track {
#[serde(rename = "@TrackID")]
pub trackid: String,
#[serde(rename = "@Name")]
pub name: Option<String>,
#[serde(rename = "@Artist")]
pub artist: Option<String>,
#[serde(rename = "@Composer")]
pub composer: Option<String>,
#[serde(rename = "@Album")]
pub album: Option<String>,
#[serde(rename = "@Grouping")]
pub grouping: Option<String>,
#[serde(rename = "@Genre")]
pub genre: Option<String>,
#[serde(rename = "@Kind")]
pub kind: Option<String>,
#[serde(rename = "@Size")]
pub size: Option<i32>,
#[serde(rename = "@TotalTime")]
pub totaltime: Option<i32>,
#[serde(rename = "@DiscNumber")]
pub discnumber: Option<i32>,
#[serde(rename = "@TrackNumber")]
pub tracknumber: Option<i32>,
#[serde(rename = "@Year")]
pub year: Option<i32>,
#[serde(rename = "@AverageBpm")]
pub averagebpm: Option<f64>,
#[serde(rename = "@DateModified")]
#[serde(skip_serializing_if = "Option::is_none")]
pub datemodified: Option<NaiveDate>,
#[serde(rename = "@DateAdded")]
#[serde(skip_serializing_if = "Option::is_none")]
pub dateadded: Option<NaiveDate>,
#[serde(rename = "@BitRate")]
pub bitrate: Option<i32>,
#[serde(rename = "@SampleRate")]
pub samplerate: Option<f64>,
#[serde(rename = "@Comments")]
pub comments: Option<String>,
#[serde(rename = "@PlayCount")]
pub playcount: Option<i32>,
#[serde(rename = "@LastPlayed")]
#[serde(skip_serializing_if = "Option::is_none")]
pub lastplayed: Option<NaiveDate>,
#[serde(rename = "@Rating")]
pub rating: Option<i32>,
#[serde(rename = "@Location")]
#[serde(serialize_with = "serialize_location")]
#[serde(deserialize_with = "deserialize_location")]
pub location: String,
#[serde(rename = "@Remixer")]
pub remixer: Option<String>,
#[serde(rename = "@Tonality")]
pub tonality: Option<String>,
#[serde(rename = "@Label")]
pub label: Option<String>,
#[serde(rename = "@Mix")]
pub mix: Option<String>,
#[serde(rename = "@Colour")]
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(rename = "TEMPO")]
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub tempos: Vec<Tempo>,
#[serde(rename = "POSITION_MARK")]
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub position_marks: Vec<PositionMark>,
}
impl Track {
pub fn new(trackid: &str, location: &str) -> Self {
Self {
trackid: trackid.to_string(),
location: location.to_string(),
..Default::default()
}
}
}
impl Default for Track {
fn default() -> Self {
Self {
trackid: String::new(),
location: String::new(),
name: None,
artist: None,
composer: None,
album: None,
grouping: None,
genre: None,
kind: None,
size: None,
totaltime: None,
discnumber: None,
tracknumber: None,
year: None,
averagebpm: None,
datemodified: None,
dateadded: None,
bitrate: None,
samplerate: None,
comments: None,
playcount: None,
lastplayed: None,
rating: None,
remixer: None,
tonality: None,
label: None,
mix: None,
color: None,
tempos: Vec::new(),
position_marks: Vec::new(),
}
}
}
impl Display for Track {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"TrackID: {}, Name: {}, Location: {}",
self.trackid,
self.name.as_deref().unwrap_or(""),
self.location
)
}
}
#[derive(Debug, PartialEq, Clone, Deserialize)]
pub struct Collection {
#[serde(rename = "TRACK")]
pub tracks: Vec<Track>,
}
impl Serialize for Collection {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Value<'a> {
#[serde(rename = "@Entries")]
entries: usize,
#[serde(rename = "TRACK")]
tracks: &'a Vec<Track>,
}
let tracks: &Vec<Track> = self.tracks.as_ref();
let value = Value {
entries: tracks.len(),
tracks,
};
value.serialize(serializer)
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct PlaylistTrack {
#[serde(rename = "@Key")]
pub key: String,
}
impl PlaylistTrack {
pub fn new(key: &str) -> Self {
Self {
key: key.to_string(),
}
}
pub fn from_track(track: &Track, key_type: &PlaylistKeyType) -> Self {
let key = match key_type {
PlaylistKeyType::TrackID => track.trackid.clone(),
PlaylistKeyType::Location => track.location.clone(),
};
Self { key }
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum PlaylistNodeType {
Folder, Playlist, }
impl Into<u16> for PlaylistNodeType {
fn into(self) -> u16 {
match self {
PlaylistNodeType::Folder => 0,
PlaylistNodeType::Playlist => 1,
}
}
}
impl TryFrom<u16> for PlaylistNodeType {
type Error = String;
fn try_from(value: u16) -> std::result::Result<Self, Self::Error> {
match value {
0 => Ok(PlaylistNodeType::Folder),
1 => Ok(PlaylistNodeType::Playlist),
_ => Err(format!("Invalid PlaylistNodeType value: {}", value)),
}
}
}
impl<'de> Deserialize<'de> for PlaylistNodeType {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"1" => Ok(PlaylistNodeType::Playlist),
"0" => Ok(PlaylistNodeType::Folder),
_ => Err(D::Error::custom(format!("Invalid node type: {}", s))),
}
}
}
impl Serialize for PlaylistNodeType {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = match self {
PlaylistNodeType::Playlist => "1",
PlaylistNodeType::Folder => "0",
};
serializer.serialize_str(s)
}
}
#[repr(i32)]
#[derive(Debug, PartialEq, Clone)]
pub enum PlaylistKeyType {
TrackID = 0, Location = 1, }
impl Into<u16> for PlaylistKeyType {
fn into(self) -> u16 {
match self {
PlaylistKeyType::TrackID => 0,
PlaylistKeyType::Location => 1,
}
}
}
impl TryFrom<u16> for PlaylistKeyType {
type Error = String;
fn try_from(value: u16) -> std::result::Result<Self, Self::Error> {
match value {
0 => Ok(PlaylistKeyType::TrackID),
1 => Ok(PlaylistKeyType::Location),
_ => Err(format!("Invalid PlaylistKeyType value: {}", value)),
}
}
}
impl<'de> Deserialize<'de> for PlaylistKeyType {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"0" => Ok(PlaylistKeyType::TrackID),
"1" => Ok(PlaylistKeyType::Location),
_ => Err(D::Error::custom(format!("Invalid node type: {}", s))),
}
}
}
impl Serialize for PlaylistKeyType {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = match self {
PlaylistKeyType::TrackID => "0",
PlaylistKeyType::Location => "1",
};
serializer.serialize_str(s)
}
}
impl TryFrom<i32> for PlaylistKeyType {
type Error = &'static str;
fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
match value {
0 => Ok(PlaylistKeyType::TrackID),
1 => Ok(PlaylistKeyType::Location),
_ => Err("Invalid value for PlaylistKeyType"),
}
}
}
#[derive(PartialEq, Clone, Deserialize)]
pub struct PlaylistNode {
#[serde(rename = "@Name")]
pub name: String,
#[serde(rename = "@Type")]
pub node_type: PlaylistNodeType,
#[serde(rename = "@KeyType")]
pub key_type: Option<PlaylistKeyType>,
#[serde(rename = "TRACK")]
pub tracks: Option<Vec<PlaylistTrack>>,
#[serde(rename = "NODE")]
pub nodes: Option<Vec<PlaylistNode>>,
}
impl Serialize for PlaylistNode {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Folder<'a> {
#[serde(rename = "@Name")]
name: &'a String,
#[serde(rename = "@Type")]
node_type: &'a PlaylistNodeType,
#[serde(rename = "@Count")]
count: usize,
#[serde(rename = "NODE")]
#[serde(skip_serializing_if = "Vec::is_empty")]
nodes: &'a Vec<PlaylistNode>,
}
#[derive(Serialize)]
struct Playlist<'a> {
#[serde(rename = "@Name")]
name: &'a String,
#[serde(rename = "@Type")]
node_type: &'a PlaylistNodeType,
#[serde(rename = "@Entries")]
entries: usize,
#[serde(rename = "@KeyType")]
key_type: &'a PlaylistKeyType,
#[serde(rename = "TRACK")]
#[serde(skip_serializing_if = "Vec::is_empty")]
tracks: &'a Vec<PlaylistTrack>,
}
match self.node_type {
PlaylistNodeType::Playlist => {
let key_type = self.key_type.as_ref().unwrap();
let empty = Vec::new();
let children: &Vec<PlaylistTrack> = self.tracks.as_ref().unwrap_or(&empty);
let value = Playlist {
name: &self.name,
node_type: &PlaylistNodeType::Playlist,
entries: children.len(),
key_type: &key_type,
tracks: children,
};
value.serialize(serializer)
}
PlaylistNodeType::Folder => {
let empty = Vec::new();
let children: &Vec<PlaylistNode> = self.nodes.as_ref().unwrap_or(&empty);
let value = Folder {
name: &self.name,
node_type: &PlaylistNodeType::Folder,
count: children.len(),
nodes: children,
};
value.serialize(serializer)
}
}
}
}
impl PlaylistNode {
pub fn new(name: &str, node_type: PlaylistNodeType) -> Self {
Self {
name: name.to_string(),
node_type,
key_type: None,
tracks: None,
nodes: None,
}
}
pub fn playlist(name: &str, key_type: PlaylistKeyType) -> Self {
let mut node = Self::new(name, PlaylistNodeType::Playlist);
node.tracks = Some(Vec::new());
node.key_type = Some(key_type);
node
}
pub fn folder(name: &str) -> Self {
let mut node = Self::new(name, PlaylistNodeType::Folder);
node.nodes = Some(Vec::new());
node
}
pub fn add_node(&mut self, node: PlaylistNode) {
self.nodes.as_mut().unwrap().push(node);
}
pub fn new_playlist(
&mut self,
name: &str,
key_type: PlaylistKeyType,
) -> Result<&mut PlaylistNode> {
if self.node_type == PlaylistNodeType::Playlist {
return Err(Error::XmlError(
"Cannot add child node to playlist node".into(),
));
};
let idx = self.nodes.as_ref().unwrap().len();
let node = Self::playlist(name, key_type);
self.add_node(node);
let node_out = self.nodes.as_mut().unwrap().get_mut(idx).unwrap();
Ok(node_out)
}
pub fn new_folder(&mut self, name: &str) -> Result<&mut PlaylistNode> {
if self.node_type == PlaylistNodeType::Playlist {
return Err(Error::XmlError(
"Cannot add child node to playlist node".into(),
));
};
let idx = self.nodes.as_ref().unwrap().len();
let node = Self::folder(name);
self.add_node(node);
let node_out = self.nodes.as_mut().unwrap().get_mut(idx).unwrap();
Ok(node_out)
}
pub fn remove_node(&mut self, index: usize) -> Result<()> {
if self.node_type == PlaylistNodeType::Playlist {
return Err(Error::XmlError(
"Cannot add child node to playlist node".into(),
));
};
self.nodes.as_mut().unwrap().remove(index);
Ok(())
}
pub fn clear_nodes(&mut self) -> Result<()> {
if self.node_type == PlaylistNodeType::Playlist {
return Err(Error::XmlError(
"Cannot add child node to playlist node".into(),
));
};
self.nodes.as_mut().unwrap().clear();
Ok(())
}
pub fn get_child(&mut self, idx: usize) -> Result<Option<&mut PlaylistNode>> {
if self.node_type == PlaylistNodeType::Playlist {
return Err(Error::XmlError("Cannot get child of playlist node".into()));
};
let nodes = self.nodes.as_mut().unwrap();
Ok(nodes.get_mut(idx))
}
pub fn find_child(&mut self, name: &str) -> Result<Option<&mut PlaylistNode>> {
if self.node_type == PlaylistNodeType::Playlist {
return Err(Error::XmlError("Cannot get child of playlist node".into()));
};
let nodes = self.nodes.as_mut().unwrap();
let node = nodes.iter_mut().find(|n| n.name == name);
Ok(node)
}
pub fn get_child_by_path(&mut self, path: Vec<String>) -> Result<Option<&mut PlaylistNode>> {
let mut parent = self;
for name in path {
let child = parent.find_child(&name)?;
if child.is_none() {
return Ok(None);
}
parent = child.unwrap();
}
Ok(Some(parent))
}
pub fn get_track(&mut self, idx: usize) -> Result<Option<&PlaylistTrack>> {
if self.node_type == PlaylistNodeType::Folder {
return Err(Error::XmlError("Cannot get track of folder node".into()));
};
let tracks = self.tracks.as_mut().unwrap();
Ok(tracks.get(idx))
}
pub fn new_track(&mut self, key: &str) -> Result<()> {
if self.node_type == PlaylistNodeType::Folder {
return Err(Error::XmlError("Cannot add track to folder node".into()));
};
let track_item = PlaylistTrack::new(key);
self.tracks.as_mut().unwrap().push(track_item);
Ok(())
}
pub fn add_track_key(&mut self, key: &str) -> Result<()> {
if self.node_type == PlaylistNodeType::Folder {
return Err(Error::XmlError("Cannot add track to folder node".into()));
};
let track_item = PlaylistTrack::new(key);
self.tracks.as_mut().unwrap().push(track_item);
Ok(())
}
pub fn add_track(&mut self, track: &Track) -> Result<()> {
if self.node_type == PlaylistNodeType::Folder {
return Err(Error::XmlError("Cannot add track to folder node".into()));
};
let key_type = self.key_type.as_ref().unwrap();
let track_item = PlaylistTrack::from_track(track, key_type);
self.tracks.as_mut().unwrap().push(track_item);
Ok(())
}
pub fn remove_track(&mut self, key: &str) -> Result<()> {
if self.node_type == PlaylistNodeType::Folder {
return Err(Error::XmlError(
"Cannot remove track from folder node".into(),
));
};
let mut index: Option<usize> = None;
for (i, track) in self.tracks.as_mut().unwrap().iter_mut().enumerate() {
if track.key == key {
index = Some(i);
}
}
if index.is_none() {
return Err(Error::XmlError("Track not found".into()));
}
self.tracks.as_mut().unwrap().remove(index.unwrap());
Ok(())
}
pub fn clear_tracks(&mut self) -> Result<()> {
if self.node_type == PlaylistNodeType::Folder {
return Err(Error::XmlError(
"Cannot clear tracks of a folder node".into(),
));
};
self.tracks.as_mut().unwrap().clear();
Ok(())
}
}
impl Debug for PlaylistNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.node_type {
PlaylistNodeType::Playlist => f
.debug_struct("PlaylistNode")
.field("name", &self.name)
.field("key_type", &self.key_type.clone().unwrap())
.finish(),
PlaylistNodeType::Folder => f
.debug_struct("PlaylistNode")
.field("name", &self.name)
.finish(),
}
}
}
impl Display for PlaylistNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.node_type {
PlaylistNodeType::Playlist => {
write!(f, "Playlist: {}", self.name)
}
PlaylistNodeType::Folder => {
write!(f, "Folder: {}", self.name)
}
}
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Playlists {
#[serde(rename = "NODE")]
node: PlaylistNode,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(rename = "MASTER_PLAYLIST")]
pub struct Document {
#[serde(rename = "@Version")]
pub version: String,
#[serde(rename = "PRODUCT")]
pub product: Product,
#[serde(rename = "COLLECTION")]
pub collection: Collection,
#[serde(rename = "PLAYLISTS")]
pub playlists: Playlists,
}
impl Document {
pub fn new() -> Self {
let version = "1.0.0";
let product_name = "rbox";
let product_version = "0.1.0";
let product_company = "rbox";
Document {
version: version.to_string(),
product: Product {
name: product_name.to_string(),
version: product_version.to_string(),
company: product_company.to_string(),
},
collection: Collection { tracks: Vec::new() },
playlists: Playlists {
node: PlaylistNode::folder("ROOT"),
},
}
}
}
pub struct RekordboxXml {
path: PathBuf,
doc: Document,
}
impl Debug for RekordboxXml {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RekordboxXml")
.field("path", &self.path)
.field("doc", &self.doc)
.finish()
}
}
impl RekordboxXml {
pub fn new<P: AsRef<Path> + AsRef<OsStr>>(path: P) -> Self {
let p = Path::new(&path).to_path_buf();
let doc = Document::new();
let xml = RekordboxXml { path: p, doc };
xml
}
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(&p).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");
let xml = RekordboxXml { path: p, doc };
xml
}
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.utf8_error())?;
let mut string = String::new();
let mut ser = quick_xml::se::Serializer::new(&mut string);
ser.indent(' ', 2);
self.doc
.serialize(ser)
.map_err(|e| Error::XmlError(e.to_string()))?;
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_tracks(&mut self) -> &mut Vec<Track> {
&mut self.doc.collection.tracks
}
pub fn set_tracks(&mut self, tracks: Vec<Track>) {
self.doc.collection.tracks = tracks;
}
pub fn get_track(&mut self, index: usize) -> Option<&mut Track> {
self.doc.collection.tracks.get_mut(index)
}
pub fn get_track_by_id(&mut self, track_id: &str) -> Option<&mut Track> {
let item = self
.doc
.collection
.tracks
.iter_mut()
.find(|t| &t.trackid == track_id);
if let Some(item) = item {
Some(item)
} else {
None
}
}
pub fn get_track_by_location(&mut self, location: &str) -> Option<&mut Track> {
let item = self
.doc
.collection
.tracks
.iter_mut()
.find(|t| t.location == location);
if let Some(item) = item {
Some(item)
} else {
None
}
}
pub fn get_track_by_key(&mut self, key: &str, key_type: PlaylistKeyType) -> Option<&mut Track> {
match key_type {
PlaylistKeyType::TrackID => self.get_track_by_id(key),
PlaylistKeyType::Location => self.get_track_by_location(key),
}
}
pub fn add_track(&mut self, track: Track) {
self.doc.collection.tracks.push(track);
}
pub fn new_track(&mut self, trackid: &str, location: &str) -> &mut Track {
let track = Track::new(trackid, location);
let index = self.doc.collection.tracks.len();
self.doc.collection.tracks.push(track);
self.doc.collection.tracks.get_mut(index).unwrap()
}
pub fn update_track(&mut self, track: Track) -> Result<()> {
if let Some(existing_track) = self
.doc
.collection
.tracks
.iter_mut()
.find(|t| t.trackid == track.trackid)
{
*existing_track = track;
Ok(())
} else {
Err(Error::XmlError("Track not found".into()))
}
}
pub fn remove_track(&mut self, track_id: &str) -> Result<()> {
let index = self
.doc
.collection
.tracks
.iter()
.position(|t| t.trackid == track_id)
.ok_or_else(|| Error::XmlError("Track not found".into()))?;
self.doc.collection.tracks.remove(index);
Ok(())
}
pub fn clear_tracks(&mut self) {
self.doc.collection.tracks.clear()
}
pub fn get_playlist_root(&mut self) -> &mut PlaylistNode {
&mut self.doc.playlists.node
}
pub fn set_playlist_root(&mut self, node: PlaylistNode) {
self.doc.playlists.node = node;
}
pub fn get_playlist_by_path(&mut self, path: Vec<String>) -> Result<Option<&mut PlaylistNode>> {
let root = &mut self.doc.playlists.node;
root.get_child_by_path(path)
}
pub fn new_playlist(
&mut self,
name: &str,
key_type: PlaylistKeyType,
parent_path: Vec<String>,
) -> Result<()> {
let parent = self.get_playlist_by_path(parent_path)?;
if parent.is_none() {
return Err(Error::XmlError("Parent playlist not found".into()));
}
parent.unwrap().new_playlist(name, key_type)?;
Ok(())
}
pub fn new_folder(&mut self, name: &str, parent_path: Vec<String>) -> Result<()> {
let parent = self.get_playlist_by_path(parent_path)?;
if parent.is_none() {
return Err(Error::XmlError("Parent playlist not found".into()));
}
parent.unwrap().new_folder(name)?;
Ok(())
}
pub fn add_track_key_to_playlist(&mut self, key: &str, parent_path: Vec<String>) -> Result<()> {
let parent = self.get_playlist_by_path(parent_path)?;
if parent.is_none() {
return Err(Error::XmlError("Parent playlist not found".into()));
}
parent.unwrap().new_track(key)?;
Ok(())
}
pub fn add_track_to_playlist(&mut self, track: &Track, parent_path: Vec<String>) -> Result<()> {
let parent = self.get_playlist_by_path(parent_path)?;
if parent.is_none() {
return Err(Error::XmlError("Parent playlist not found".into()));
}
parent.unwrap().add_track(track)?;
Ok(())
}
}