use crate::util::{AnyFileThing, loadfile_read, loadfile_write};
use crate::{AudexError, Result};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::collections::HashMap;
use std::fmt;
use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom, Write};
use std::path::Path;
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PaddingInfo {
pub padding: i64,
pub size: i64,
}
impl PaddingInfo {
pub fn new(padding: i64, size: i64) -> Self {
Self { padding, size }
}
pub fn get_default_padding(&self) -> i64 {
let clamped_size = self.size.max(0);
let high = (1024i64 * 10).saturating_add(clamped_size / 100); let low = 1024i64.saturating_add(clamped_size / 1000);
if self.padding >= 0 {
if self.padding > high {
low
} else {
self.padding
}
} else {
low
}
}
pub(crate) fn get_padding_with<F>(&self, user_func: Option<F>) -> i64
where
F: FnOnce(&PaddingInfo) -> i64,
{
match user_func {
Some(func) => func(self),
None => self.get_default_padding(),
}
}
}
impl fmt::Display for PaddingInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"<PaddingInfo size={} padding={}>",
self.size, self.padding
)
}
}
pub trait Tags {
fn get(&self, key: &str) -> Option<&[String]>;
fn set(&mut self, key: &str, values: Vec<String>);
fn remove(&mut self, key: &str);
fn keys(&self) -> Vec<String>;
fn contains_key(&self, key: &str) -> bool {
self.get(key).is_some()
}
fn get_first(&self, key: &str) -> Option<&String> {
self.get(key)?.first()
}
fn set_single(&mut self, key: &str, value: String) {
self.set(key, vec![value]);
}
fn values(&self) -> Vec<Vec<String>> {
let keys = self.keys();
keys.iter()
.filter_map(|k| self.get(k).map(|v| v.to_vec()))
.collect()
}
fn items(&self) -> Vec<(String, Vec<String>)> {
let keys = self.keys();
keys.iter()
.filter_map(|k| self.get(k).map(|v| (k.clone(), v.to_vec())))
.collect()
}
fn pprint(&self) -> String;
fn module_name(&self) -> &'static str {
"audex"
}
}
pub trait Metadata: Tags {
type Error: Into<AudexError>;
fn new() -> Self
where
Self: Sized;
fn load_from_path<P: AsRef<Path>>(path: P) -> Result<Self>
where
Self: Sized,
{
let mut file_thing = loadfile_read(path)?;
Self::load_from_fileobj(&mut file_thing)
}
fn load_from_fileobj(filething: &mut AnyFileThing) -> Result<Self>
where
Self: Sized;
fn save_to_path<P: AsRef<Path>>(&self, path: Option<P>) -> Result<()> {
match path {
Some(path) => {
let mut file_thing = loadfile_write(path)?;
self.save_to_fileobj(&mut file_thing)?;
file_thing.write_back()
}
None => Err(AudexError::InvalidOperation(
"No file path provided".to_string(),
)),
}
}
fn save_to_fileobj(&self, filething: &mut AnyFileThing) -> Result<()>;
fn delete_from_path<P: AsRef<Path>>(path: Option<P>) -> Result<()>
where
Self: Sized,
{
match path {
Some(path) => {
let mut file_thing = loadfile_write(path)?;
Self::delete_from_fileobj(&mut file_thing)?;
file_thing.write_back()
}
None => Err(AudexError::InvalidOperation(
"No file path provided".to_string(),
)),
}
}
fn delete_from_fileobj(filething: &mut AnyFileThing) -> Result<()>
where
Self: Sized;
}
pub trait MetadataFields {
fn artist(&self) -> Option<&String>;
fn set_artist(&mut self, artist: String);
fn album(&self) -> Option<&String>;
fn set_album(&mut self, album: String);
fn title(&self) -> Option<&String>;
fn set_title(&mut self, title: String);
fn track_number(&self) -> Option<u32>;
fn set_track_number(&mut self, track: u32);
fn date(&self) -> Option<&String>;
fn set_date(&mut self, date: String);
fn genre(&self) -> Option<&String>;
fn set_genre(&mut self, genre: String);
}
const BASIC_TAGS_MAGIC: &[u8; 8] = b"ADXBTAGS";
fn write_len_prefixed_string<W: Write>(writer: &mut W, value: &str) -> Result<()> {
let bytes = value.as_bytes();
if bytes.len() > u32::MAX as usize {
return Err(AudexError::InvalidData(
"String too large for tag serialization".to_string(),
));
}
writer.write_u32::<LittleEndian>(bytes.len() as u32)?;
writer.write_all(bytes)?;
Ok(())
}
const MAX_STRING_LENGTH: u32 = 10 * 1024 * 1024;
const MAX_ENTRY_COUNT: u32 = 100_000;
const MAX_VALUE_COUNT: u32 = 100_000;
const MAX_TOTAL_VALUES: u64 = 1_000_000;
const MAX_TOTAL_STRING_BYTES: u64 = 100 * 1024 * 1024;
fn read_len_prefixed_string_counted<R: Read>(
reader: &mut R,
total_bytes: &mut u64,
) -> Result<String> {
let len = reader.read_u32::<LittleEndian>()?;
if len > MAX_STRING_LENGTH {
return Err(AudexError::InvalidData(format!(
"BasicTags string length {} exceeds {} byte limit",
len, MAX_STRING_LENGTH
)));
}
*total_bytes = total_bytes.checked_add(len as u64).ok_or_else(|| {
AudexError::InvalidData("BasicTags string byte count overflow".to_string())
})?;
if *total_bytes > MAX_TOTAL_STRING_BYTES {
return Err(AudexError::InvalidData(format!(
"BasicTags cumulative string bytes {} exceed {} byte limit",
*total_bytes, MAX_TOTAL_STRING_BYTES
)));
}
let mut buf = vec![0u8; len as usize];
reader.read_exact(&mut buf)?;
String::from_utf8(buf).map_err(|e| {
AudexError::InvalidData(format!("Invalid UTF-8 data in BasicTags store: {}", e))
})
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BasicTags {
tags: HashMap<String, Vec<String>>,
}
impl BasicTags {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
tags: HashMap::with_capacity(capacity),
}
}
}
impl Tags for BasicTags {
fn get(&self, key: &str) -> Option<&[String]> {
self.tags.get(key).map(|v| v.as_slice())
}
fn set(&mut self, key: &str, values: Vec<String>) {
if values.is_empty() {
self.tags.remove(key);
} else {
self.tags.insert(key.to_string(), values);
}
}
fn remove(&mut self, key: &str) {
self.tags.remove(key);
}
fn keys(&self) -> Vec<String> {
self.tags.keys().cloned().collect()
}
fn pprint(&self) -> String {
let mut result = String::new();
let mut keys: Vec<_> = self.keys();
keys.sort();
for key in keys {
if let Some(values) = self.get(&key) {
for value in values {
result.push_str(&format!("{}={}\n", key, value));
}
}
}
result
}
}
impl Metadata for BasicTags {
type Error = AudexError;
fn new() -> Self {
BasicTags::new()
}
fn load_from_fileobj(filething: &mut AnyFileThing) -> Result<Self> {
filething.seek(SeekFrom::Start(0))?;
let mut magic = [0u8; BASIC_TAGS_MAGIC.len()];
match filething.read_exact(&mut magic) {
Ok(()) => {}
Err(ref err) if err.kind() == ErrorKind::UnexpectedEof => {
return Ok(BasicTags::new());
}
Err(err) => return Err(err.into()),
}
if magic != *BASIC_TAGS_MAGIC {
return Err(AudexError::InvalidData(
"Invalid BasicTags storage header".to_string(),
));
}
let mut tags = BasicTags::new();
let entry_count = filething.read_u32::<LittleEndian>()?;
if entry_count > MAX_ENTRY_COUNT {
return Err(AudexError::InvalidData(format!(
"BasicTags entry count {} exceeds {} limit",
entry_count, MAX_ENTRY_COUNT
)));
}
let mut cumulative_values: u64 = 0;
let mut cumulative_string_bytes: u64 = 0;
for _ in 0..entry_count {
let key = read_len_prefixed_string_counted(filething, &mut cumulative_string_bytes)?;
let value_count = filething.read_u32::<LittleEndian>()?;
if value_count > MAX_VALUE_COUNT {
return Err(AudexError::InvalidData(format!(
"BasicTags value count {} exceeds {} limit",
value_count, MAX_VALUE_COUNT
)));
}
cumulative_values += value_count as u64;
if cumulative_values > MAX_TOTAL_VALUES {
return Err(AudexError::InvalidData(format!(
"BasicTags cumulative value count {} exceeds {} limit",
cumulative_values, MAX_TOTAL_VALUES
)));
}
let capped_capacity = std::cmp::min(value_count as usize, 256);
let mut values = Vec::with_capacity(capped_capacity);
for _ in 0..value_count {
values.push(read_len_prefixed_string_counted(
filething,
&mut cumulative_string_bytes,
)?);
}
tags.set(&key, values);
}
Ok(tags)
}
fn save_to_fileobj(&self, filething: &mut AnyFileThing) -> Result<()> {
let mut buffer = Cursor::new(Vec::new());
buffer.write_all(BASIC_TAGS_MAGIC)?;
if self.tags.len() > u32::MAX as usize {
return Err(AudexError::InvalidData(format!(
"Tag count {} exceeds maximum of {}",
self.tags.len(),
u32::MAX
)));
}
buffer.write_u32::<LittleEndian>(self.tags.len() as u32)?;
for (key, values) in &self.tags {
write_len_prefixed_string(&mut buffer, key)?;
if values.len() > u32::MAX as usize {
return Err(AudexError::InvalidData(format!(
"Value count for key '{}' exceeds maximum of {}",
key,
u32::MAX
)));
}
buffer.write_u32::<LittleEndian>(values.len() as u32)?;
for value in values {
write_len_prefixed_string(&mut buffer, value)?;
}
}
let data = buffer.into_inner();
filething.truncate(0)?;
filething.seek(SeekFrom::Start(0))?;
filething.write_all(&data)?;
filething.flush()?;
Ok(())
}
fn delete_from_fileobj(filething: &mut AnyFileThing) -> Result<()> {
filething.truncate(0)?;
filething.seek(SeekFrom::Start(0))?;
filething.flush()?;
Ok(())
}
}
impl MetadataFields for BasicTags {
fn artist(&self) -> Option<&String> {
self.get_first("ARTIST")
.or_else(|| self.get_first("artist"))
}
fn set_artist(&mut self, artist: String) {
self.set_single("ARTIST", artist);
}
fn album(&self) -> Option<&String> {
self.get_first("ALBUM").or_else(|| self.get_first("album"))
}
fn set_album(&mut self, album: String) {
self.set_single("ALBUM", album);
}
fn title(&self) -> Option<&String> {
self.get_first("TITLE").or_else(|| self.get_first("title"))
}
fn set_title(&mut self, title: String) {
self.set_single("TITLE", title);
}
fn track_number(&self) -> Option<u32> {
self.get_first("TRACKNUMBER")
.or_else(|| self.get_first("tracknumber"))
.and_then(|s| s.parse().ok())
}
fn set_track_number(&mut self, track: u32) {
self.set_single("TRACKNUMBER", track.to_string());
}
fn date(&self) -> Option<&String> {
self.get_first("DATE").or_else(|| self.get_first("date"))
}
fn set_date(&mut self, date: String) {
self.set_single("DATE", date);
}
fn genre(&self) -> Option<&String> {
self.get_first("GENRE").or_else(|| self.get_first("genre"))
}
fn set_genre(&mut self, genre: String) {
self.set_single("GENRE", genre);
}
}