use crate::config::{ParsingMode, WriteOptions};
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::{FrameFlags, FrameHeader, FrameId};
use crate::macros::parse_mode_choice;
use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text};
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use std::io::Read;
const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("UFID"));
#[derive(Clone, Debug, Eq)]
pub struct UniqueFileIdentifierFrame<'a> {
pub(crate) header: FrameHeader<'a>,
pub owner: Cow<'a, str>,
pub identifier: Cow<'a, [u8]>,
}
impl PartialEq for UniqueFileIdentifierFrame<'_> {
fn eq(&self, other: &Self) -> bool {
self.owner == other.owner
}
}
impl Hash for UniqueFileIdentifierFrame<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.owner.hash(state);
}
}
impl<'a> UniqueFileIdentifierFrame<'a> {
pub fn new(owner: impl Into<Cow<'a, str>>, identifier: impl Into<Cow<'a, [u8]>>) -> Self {
let header = FrameHeader::new(FRAME_ID, FrameFlags::default());
Self {
header,
owner: owner.into(),
identifier: identifier.into(),
}
}
pub fn id(&self) -> FrameId<'_> {
FRAME_ID
}
pub fn flags(&self) -> FrameFlags {
self.header.flags
}
pub fn set_flags(&mut self, flags: FrameFlags) {
self.header.flags = flags;
}
pub fn parse<R>(
reader: &mut R,
frame_flags: FrameFlags,
parse_mode: ParsingMode,
) -> Result<Option<Self>>
where
R: Read,
{
let owner_decode_result = decode_text(
reader,
TextDecodeOptions::new()
.encoding(TextEncoding::Latin1)
.terminated(true),
)?;
let owner;
match owner_decode_result.text_or_none() {
Some(valid) => owner = valid,
None => {
parse_mode_choice!(
parse_mode,
BESTATTEMPT: owner = String::new(),
DEFAULT: return Err(Id3v2Error::new(Id3v2ErrorKind::MissingUfidOwner).into())
);
},
}
let mut identifier = Vec::new();
reader.read_to_end(&mut identifier)?;
let header = FrameHeader::new(FRAME_ID, frame_flags);
Ok(Some(Self {
header,
owner: Cow::Owned(owner),
identifier: Cow::Owned(identifier),
}))
}
pub fn as_bytes(&self, write_options: WriteOptions) -> Result<Vec<u8>> {
let Self {
owner, identifier, ..
} = self;
let mut content = Vec::with_capacity(owner.len() + 1 + identifier.len());
content.extend(TextEncoding::Latin1.encode(
owner,
true,
write_options.lossy_text_encoding,
)?);
content.extend_from_slice(identifier);
Ok(content)
}
}
impl UniqueFileIdentifierFrame<'static> {
pub(crate) fn downgrade(&self) -> UniqueFileIdentifierFrame<'_> {
UniqueFileIdentifierFrame {
header: self.header.downgrade(),
owner: Cow::Borrowed(&self.owner),
identifier: Cow::Borrowed(&self.identifier),
}
}
}
#[cfg(test)]
mod tests {
use crate::config::WriteOptions;
use crate::id3::v2::FrameFlags;
#[test_log::test]
fn issue_204_invalid_ufid_parsing_mode_best_attempt() {
use crate::config::ParsingMode;
use crate::id3::v2::UniqueFileIdentifierFrame;
let ufid_no_owner = UniqueFileIdentifierFrame::new("", vec![0]);
let bytes = ufid_no_owner.as_bytes(WriteOptions::default()).unwrap();
assert!(
UniqueFileIdentifierFrame::parse(
&mut &bytes[..],
FrameFlags::default(),
ParsingMode::Strict
)
.is_err()
);
assert!(
UniqueFileIdentifierFrame::parse(
&mut &bytes[..],
FrameFlags::default(),
ParsingMode::BestAttempt
)
.is_ok()
);
}
}