use crate::{v1, Error, ErrorKind, StorageFile, Tag, Version};
use std::fs;
use std::fs::File;
use std::io;
use std::path::Path;
pub fn is_candidate(mut file: impl io::Read + io::Seek) -> crate::Result<FormatVersion> {
let v2 = Tag::is_candidate(&mut file)?;
let v1 = v1::Tag::is_candidate(&mut file)?;
Ok(match (v1, v2) {
(false, false) => FormatVersion::None,
(true, false) => FormatVersion::Id3v1,
(false, true) => FormatVersion::Id3v2,
(true, true) => FormatVersion::Both,
})
}
pub fn is_candidate_path(path: impl AsRef<Path>) -> crate::Result<FormatVersion> {
is_candidate(File::open(path)?)
}
pub fn read_from(mut file: impl io::Read + io::Seek) -> crate::Result<Tag> {
match Tag::read_from2(&mut file) {
Err(Error {
kind: ErrorKind::NoTag,
..
}) => {}
Err(err) => return Err(err),
Ok(tag) => return Ok(tag),
}
match v1::Tag::read_from(file) {
Err(Error {
kind: ErrorKind::NoTag,
..
}) => {}
Err(err) => return Err(err),
Ok(tag) => return Ok(tag.into()),
}
Err(Error::new(
ErrorKind::NoTag,
"Neither a ID3v2 or ID3v1 tag was found",
))
}
pub fn read_from_path(path: impl AsRef<Path>) -> crate::Result<Tag> {
read_from(File::open(path)?)
}
pub fn write_to_file(mut file: impl StorageFile, tag: &Tag, version: Version) -> crate::Result<()> {
tag.write_to_file(&mut file, version)?;
v1::Tag::remove_from_file(&mut file)?;
Ok(())
}
pub fn write_to_path(path: impl AsRef<Path>, tag: &Tag, version: Version) -> crate::Result<()> {
let file = fs::OpenOptions::new().read(true).write(true).open(path)?;
write_to_file(file, tag, version)
}
pub fn remove_from_path(path: impl AsRef<Path>) -> crate::Result<FormatVersion> {
let v2 = Tag::remove_from_path(&path)?;
let v1 = v1::Tag::remove_from_path(path)?;
Ok(match (v1, v2) {
(false, false) => FormatVersion::None,
(true, false) => FormatVersion::Id3v1,
(false, true) => FormatVersion::Id3v2,
(true, true) => FormatVersion::Both,
})
}
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum FormatVersion {
None,
Id3v1,
Id3v2,
Both,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TagLike;
use std::fs::File;
use std::io::{copy, Write};
fn file_with_both_formats() -> tempfile::NamedTempFile {
let mut v2_testdata = File::open("testdata/id3v24.id3").unwrap();
let mut v1_testdata = File::open("testdata/id3v1.id3").unwrap();
let mut tmp = tempfile::NamedTempFile::new().unwrap();
copy(&mut v2_testdata, &mut tmp).unwrap();
tmp.write_all(&[0xaa; 1337]).unwrap(); copy(&mut v1_testdata, &mut tmp).unwrap();
tmp
}
#[test]
fn test_is_candidate() {
let tmp = file_with_both_formats();
assert_eq!(is_candidate_path(&tmp).unwrap(), FormatVersion::Both);
assert_eq!(
is_candidate_path("testdata/image.jpg").unwrap(),
FormatVersion::None
);
assert_eq!(
is_candidate_path("testdata/id3v1.id3").unwrap(),
FormatVersion::Id3v1
);
assert_eq!(
is_candidate_path("testdata/id3v24.id3").unwrap(),
FormatVersion::Id3v2
);
}
#[test]
fn test_read_from_path() {
let tmp = file_with_both_formats();
let v2 = read_from_path(&tmp).unwrap();
assert_eq!(v2.genre(), Some("Genre"));
let v1 = read_from_path("testdata/id3v1.id3").unwrap();
assert_eq!(v1.genre(), Some("Trance"));
}
#[test]
fn test_write_to_path() {
let tmp = file_with_both_formats();
let mut tag = read_from_path(&tmp).unwrap();
tag.set_artist("High Contrast");
write_to_path(&tmp, &tag, Version::Id3v24).unwrap();
assert_eq!(is_candidate_path(&tmp).unwrap(), FormatVersion::Id3v2);
}
#[test]
fn test_remove_from_path() {
let tmp = file_with_both_formats();
assert_eq!(remove_from_path(&tmp).unwrap(), FormatVersion::Both);
}
}