use super::audio_file::AudioFile;
use super::file_type::FileType;
use crate::config::{ParseOptions, WriteOptions};
use crate::error::{LoftyError, Result};
use crate::properties::FileProperties;
use crate::tag::{Tag, TagExt, TagSupport, TagType};
use crate::util::io::{FileLike, Length, Truncate};
use std::io::{Read, Seek};
pub trait TaggedFileExt {
fn file_type(&self) -> FileType;
fn tags(&self) -> &[Tag];
fn primary_tag_type(&self) -> TagType {
self.file_type().primary_tag_type()
}
fn tag_support(&self, tag_type: TagType) -> TagSupport {
self.file_type().tag_support(tag_type)
}
fn tag(&self, tag_type: TagType) -> Option<&Tag>;
fn tag_mut(&mut self, tag_type: TagType) -> Option<&mut Tag>;
fn primary_tag(&self) -> Option<&Tag> {
self.tag(self.primary_tag_type())
}
fn primary_tag_mut(&mut self) -> Option<&mut Tag> {
self.tag_mut(self.primary_tag_type())
}
fn first_tag(&self) -> Option<&Tag> {
self.tags().first()
}
fn first_tag_mut(&mut self) -> Option<&mut Tag>;
fn insert_tag(&mut self, tag: Tag) -> Option<Tag>;
fn remove(&mut self, tag_type: TagType) -> Option<Tag>;
fn clear(&mut self);
}
pub struct TaggedFile {
pub(crate) ty: FileType,
pub(crate) properties: FileProperties,
pub(crate) tags: Vec<Tag>,
}
impl TaggedFile {
#[doc(hidden)]
#[must_use]
pub const fn new(ty: FileType, properties: FileProperties, tags: Vec<Tag>) -> Self {
Self {
ty,
properties,
tags,
}
}
pub fn change_file_type(&mut self, file_type: FileType) {
self.ty = file_type;
self.properties = FileProperties::default();
self.tags
.retain(|t| self.ty.tag_support(t.tag_type()).is_readable());
}
}
impl TaggedFileExt for TaggedFile {
fn file_type(&self) -> FileType {
self.ty
}
fn tags(&self) -> &[Tag] {
self.tags.as_slice()
}
fn tag(&self, tag_type: TagType) -> Option<&Tag> {
self.tags.iter().find(|i| i.tag_type() == tag_type)
}
fn tag_mut(&mut self, tag_type: TagType) -> Option<&mut Tag> {
self.tags.iter_mut().find(|i| i.tag_type() == tag_type)
}
fn first_tag_mut(&mut self) -> Option<&mut Tag> {
self.tags.first_mut()
}
fn insert_tag(&mut self, tag: Tag) -> Option<Tag> {
let tag_type = tag.tag_type();
if self.tag_support(tag_type).is_readable() {
let ret = self.remove(tag_type);
self.tags.push(tag);
return ret;
}
None
}
fn remove(&mut self, tag_type: TagType) -> Option<Tag> {
self.tags
.iter()
.position(|t| t.tag_type() == tag_type)
.map(|pos| self.tags.remove(pos))
}
fn clear(&mut self) {
self.tags.clear()
}
}
impl AudioFile for TaggedFile {
type Properties = FileProperties;
fn read_from<R>(reader: &mut R, parse_options: ParseOptions) -> Result<Self>
where
R: Read + Seek,
Self: Sized,
{
crate::probe::Probe::new(reader)
.guess_file_type()?
.options(parse_options)
.read()
}
fn save_to<F>(&self, file: &mut F, write_options: WriteOptions) -> Result<()>
where
F: FileLike,
LoftyError: From<<F as Truncate>::Error>,
LoftyError: From<<F as Length>::Error>,
{
for tag in &self.tags {
if !self.tag_support(tag.tag_type()).is_writable() {
continue;
}
file.rewind()?;
tag.save_to(file, write_options)?;
}
Ok(())
}
fn properties(&self) -> &Self::Properties {
&self.properties
}
fn contains_tag(&self) -> bool {
!self.tags.is_empty()
}
fn contains_tag_type(&self, tag_type: TagType) -> bool {
self.tags.iter().any(|t| t.tag_type() == tag_type)
}
}
impl<F> From<BoundTaggedFile<F>> for TaggedFile {
fn from(input: BoundTaggedFile<F>) -> Self {
input.inner
}
}
pub struct BoundTaggedFile<F> {
pub(crate) inner: TaggedFile,
pub(crate) file_handle: F,
}
impl<F> BoundTaggedFile<F> {
pub fn into_inner(self) -> F {
self.file_handle
}
}
impl<F: FileLike> BoundTaggedFile<F>
where
LoftyError: From<<F as Truncate>::Error>,
LoftyError: From<<F as Length>::Error>,
{
pub fn read_from(mut file: F, parse_options: ParseOptions) -> Result<Self> {
let inner = TaggedFile::read_from(&mut file, parse_options)?;
file.rewind()?;
Ok(Self {
inner,
file_handle: file,
})
}
pub fn save(&mut self, write_options: WriteOptions) -> Result<()> {
self.inner.save_to(&mut self.file_handle, write_options)?;
self.inner.tags.retain(|tag| !tag.is_empty());
Ok(())
}
}
impl<F> TaggedFileExt for BoundTaggedFile<F> {
fn file_type(&self) -> FileType {
self.inner.file_type()
}
fn tags(&self) -> &[Tag] {
self.inner.tags()
}
fn tag(&self, tag_type: TagType) -> Option<&Tag> {
self.inner.tag(tag_type)
}
fn tag_mut(&mut self, tag_type: TagType) -> Option<&mut Tag> {
self.inner.tag_mut(tag_type)
}
fn first_tag_mut(&mut self) -> Option<&mut Tag> {
self.inner.first_tag_mut()
}
fn insert_tag(&mut self, tag: Tag) -> Option<Tag> {
self.inner.insert_tag(tag)
}
fn remove(&mut self, tag_type: TagType) -> Option<Tag> {
self.inner.remove(tag_type)
}
fn clear(&mut self) {
self.inner.clear()
}
}
impl<T> AudioFile for BoundTaggedFile<T> {
type Properties = FileProperties;
fn read_from<R>(_: &mut R, _: ParseOptions) -> Result<Self>
where
R: Read + Seek,
Self: Sized,
{
unimplemented!(
"BoundTaggedFile can only be constructed through `BoundTaggedFile::read_from`"
)
}
fn save_to<F>(&self, file: &mut F, write_options: WriteOptions) -> Result<()>
where
F: FileLike,
LoftyError: From<<F as Truncate>::Error>,
LoftyError: From<<F as Length>::Error>,
{
self.inner.save_to(file, write_options)
}
fn properties(&self) -> &Self::Properties {
self.inner.properties()
}
fn contains_tag(&self) -> bool {
self.inner.contains_tag()
}
fn contains_tag_type(&self, tag_type: TagType) -> bool {
self.inner.contains_tag_type(tag_type)
}
}