Skip to content

Commit 2ddd347

Browse files
committed
misc: Make all file writes generic
1 parent 3402a69 commit 2ddd347

File tree

31 files changed

+721
-276
lines changed

31 files changed

+721
-276
lines changed

lofty_attr/src/internal.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,32 +48,32 @@ pub(crate) fn init_write_lookup(
4848
read_only: false,
4949
items: lofty::ape::tag::tagitems_into_ape(tag),
5050
}
51-
.write_to(data, write_options)
51+
.write_to(file, write_options)
5252
});
5353

5454
insert!(map, Id3v1, {
55-
Into::<lofty::id3::v1::tag::Id3v1TagRef<'_>>::into(tag).write_to(data, write_options)
55+
Into::<lofty::id3::v1::tag::Id3v1TagRef<'_>>::into(tag).write_to(file, write_options)
5656
});
5757

5858
if id3v2_strippable {
5959
insert!(map, Id3v2, {
60-
lofty::id3::v2::tag::Id3v2TagRef::empty().write_to(data, write_options)
60+
lofty::id3::v2::tag::Id3v2TagRef::empty().write_to(file, write_options)
6161
});
6262
} else {
6363
insert!(map, Id3v2, {
6464
lofty::id3::v2::tag::Id3v2TagRef {
6565
flags: lofty::id3::v2::Id3v2TagFlags::default(),
6666
frames: lofty::id3::v2::tag::tag_frames(tag),
6767
}
68-
.write_to(data, write_options)
68+
.write_to(file, write_options)
6969
});
7070
}
7171

7272
insert!(map, RiffInfo, {
7373
lofty::iff::wav::tag::RIFFInfoListRef::new(lofty::iff::wav::tag::tagitems_into_riff(
7474
tag.items(),
7575
))
76-
.write_to(data, write_options)
76+
.write_to(file, write_options)
7777
});
7878

7979
insert!(map, AiffText, {
@@ -84,7 +84,7 @@ pub(crate) fn init_write_lookup(
8484
annotations: Some(tag.get_strings(&lofty::prelude::ItemKey::Comment)),
8585
comments: None,
8686
}
87-
.write_to(data, write_options)
87+
.write_to(file, write_options)
8888
});
8989

9090
map
@@ -112,7 +112,12 @@ pub(crate) fn write_module(
112112
quote! {
113113
pub(crate) mod write {
114114
#[allow(unused_variables)]
115-
pub(crate) fn write_to(data: &mut ::std::fs::File, tag: &::lofty::tag::Tag, write_options: ::lofty::config::WriteOptions) -> ::lofty::error::Result<()> {
115+
pub(crate) fn write_to<F>(file: &mut F, tag: &::lofty::tag::Tag, write_options: ::lofty::config::WriteOptions) -> ::lofty::error::Result<()>
116+
where
117+
F: ::lofty::io::FileLike,
118+
::lofty::error::LoftyError: ::std::convert::From<<F as ::lofty::io::Truncate>::Error>,
119+
::lofty::error::LoftyError: ::std::convert::From<<F as ::lofty::io::Length>::Error>,
120+
{
116121
match tag.tag_type() {
117122
#( #applicable_formats )*
118123
_ => crate::macros::err!(UnsupportedTag),

lofty_attr/src/lofty_file.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,12 @@ fn generate_audiofile_impl(file: &LoftyFile) -> syn::Result<proc_macro2::TokenSt
445445
#read_fn(reader, parse_options)
446446
}
447447

448-
fn save_to(&self, file: &mut ::std::fs::File, write_options: ::lofty::config::WriteOptions) -> ::lofty::error::Result<()> {
448+
fn save_to<F>(&self, file: &mut F, write_options: ::lofty::config::WriteOptions) -> ::lofty::error::Result<()>
449+
where
450+
F: ::lofty::io::FileLike,
451+
::lofty::error::LoftyError: ::std::convert::From<<F as ::lofty::io::Truncate>::Error>,
452+
::lofty::error::LoftyError: ::std::convert::From<<F as ::lofty::io::Length>::Error>,
453+
{
449454
use ::lofty::tag::TagExt as _;
450455
use ::std::io::Seek as _;
451456
#save_to_body

src/ape/tag/mod.rs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ use crate::tag::{
1212
};
1313

1414
use std::borrow::Cow;
15-
use std::fs::File;
1615
use std::io::Write;
1716
use std::ops::Deref;
18-
use std::path::Path;
1917

18+
use crate::util::io::{FileLike, Truncate};
2019
use lofty_attr::tag;
2120

2221
macro_rules! impl_accessor {
@@ -304,6 +303,11 @@ impl TagExt for ApeTag {
304303
type Err = LoftyError;
305304
type RefKey<'a> = &'a str;
306305

306+
#[inline]
307+
fn tag_type(&self) -> TagType {
308+
TagType::Ape
309+
}
310+
307311
fn len(&self) -> usize {
308312
self.items.len()
309313
}
@@ -322,11 +326,15 @@ impl TagExt for ApeTag {
322326
///
323327
/// * Attempting to write the tag to a format that does not support it
324328
/// * An existing tag has an invalid size
325-
fn save_to(
329+
fn save_to<F>(
326330
&self,
327-
file: &mut File,
331+
file: &mut F,
328332
write_options: WriteOptions,
329-
) -> std::result::Result<(), Self::Err> {
333+
) -> std::result::Result<(), Self::Err>
334+
where
335+
F: FileLike,
336+
LoftyError: From<<F as Truncate>::Error>,
337+
{
330338
ApeTagRef {
331339
read_only: self.read_only,
332340
items: self.items.iter().map(Into::into),
@@ -351,14 +359,6 @@ impl TagExt for ApeTag {
351359
.dump_to(writer, write_options)
352360
}
353361

354-
fn remove_from_path<P: AsRef<Path>>(&self, path: P) -> std::result::Result<(), Self::Err> {
355-
TagType::Ape.remove_from_path(path)
356-
}
357-
358-
fn remove_from(&self, file: &mut File) -> std::result::Result<(), Self::Err> {
359-
TagType::Ape.remove_from(file)
360-
}
361-
362362
fn clear(&mut self) {
363363
self.items.clear();
364364
}
@@ -492,7 +492,11 @@ impl<'a, I> ApeTagRef<'a, I>
492492
where
493493
I: Iterator<Item = ApeItemRef<'a>>,
494494
{
495-
pub(crate) fn write_to(&mut self, file: &mut File, write_options: WriteOptions) -> Result<()> {
495+
pub(crate) fn write_to<F>(&mut self, file: &mut F, write_options: WriteOptions) -> Result<()>
496+
where
497+
F: FileLike,
498+
LoftyError: From<<F as Truncate>::Error>,
499+
{
496500
write::write_to(file, self, write_options)
497501
}
498502

src/ape/tag/write.rs

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,42 @@ use super::ApeTagRef;
33
use crate::ape::constants::APE_PREAMBLE;
44
use crate::ape::tag::read;
55
use crate::config::WriteOptions;
6-
use crate::error::Result;
6+
use crate::error::{LoftyError, Result};
77
use crate::id3::{find_id3v1, find_id3v2, find_lyrics3v2, FindId3v2Config};
88
use crate::macros::{decode_err, err};
99
use crate::probe::Probe;
1010
use crate::tag::item::ItemValueRef;
11+
use crate::util::io::{FileLike, Truncate};
1112

12-
use std::fs::File;
13-
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
13+
use std::io::{Cursor, Seek, SeekFrom, Write};
1414

1515
use byteorder::{LittleEndian, WriteBytesExt};
1616

1717
#[allow(clippy::shadow_unrelated)]
18-
pub(crate) fn write_to<'a, I>(
19-
data: &mut File,
18+
pub(crate) fn write_to<'a, F, I>(
19+
file: &mut F,
2020
tag_ref: &mut ApeTagRef<'a, I>,
2121
write_options: WriteOptions,
2222
) -> Result<()>
2323
where
2424
I: Iterator<Item = ApeItemRef<'a>>,
25+
F: FileLike,
26+
LoftyError: From<<F as Truncate>::Error>,
2527
{
26-
let probe = Probe::new(data).guess_file_type()?;
28+
let probe = Probe::new(file).guess_file_type()?;
2729

2830
match probe.file_type() {
2931
Some(ft) if super::ApeTag::SUPPORTED_FORMATS.contains(&ft) => {},
3032
_ => err!(UnsupportedTag),
3133
}
3234

33-
let data = probe.into_inner();
35+
let file = probe.into_inner();
3436

3537
// We don't actually need the ID3v2 tag, but reading it will seek to the end of it if it exists
36-
find_id3v2(data, FindId3v2Config::NO_READ_TAG)?;
38+
find_id3v2(file, FindId3v2Config::NO_READ_TAG)?;
3739

3840
let mut ape_preamble = [0; 8];
39-
data.read_exact(&mut ape_preamble)?;
41+
file.read_exact(&mut ape_preamble)?;
4042

4143
// We have to check the APE tag for any read only items first
4244
let mut read_only = None;
@@ -45,8 +47,8 @@ where
4547
// If one is found, it'll be removed and rewritten at the bottom, where it should be
4648
let mut header_ape_tag = (false, (0, 0));
4749

48-
let start = data.stream_position()?;
49-
match read::read_ape_tag(data, false)? {
50+
let start = file.stream_position()?;
51+
match read::read_ape_tag(file, false)? {
5052
Some((mut existing_tag, header)) => {
5153
if write_options.respect_read_only {
5254
// Only keep metadata around that's marked read only
@@ -60,25 +62,25 @@ where
6062
header_ape_tag = (true, (start, start + u64::from(header.size)))
6163
},
6264
None => {
63-
data.seek(SeekFrom::Current(-8))?;
65+
file.seek(SeekFrom::Current(-8))?;
6466
},
6567
}
6668

6769
// Skip over ID3v1 and Lyrics3v2 tags
68-
find_id3v1(data, false)?;
69-
find_lyrics3v2(data)?;
70+
find_id3v1(file, false)?;
71+
find_lyrics3v2(file)?;
7072

7173
// In case there's no ape tag already, this is the spot it belongs
72-
let ape_position = data.stream_position()?;
74+
let ape_position = file.stream_position()?;
7375

7476
// Now search for an APE tag at the end
75-
data.seek(SeekFrom::Current(-32))?;
77+
file.seek(SeekFrom::Current(-32))?;
7678

7779
let mut ape_tag_location = None;
7880

7981
// Also check this tag for any read only items
80-
let start = data.stream_position()? as usize + 32;
81-
if let Some((mut existing_tag, header)) = read::read_ape_tag(data, true)? {
82+
let start = file.stream_position()? as usize + 32;
83+
if let Some((mut existing_tag, header)) = read::read_ape_tag(file, true)? {
8284
if write_options.respect_read_only {
8385
existing_tag.items.retain(|i| i.read_only);
8486

@@ -114,10 +116,10 @@ where
114116
tag = create_ape_tag(tag_ref, std::iter::empty(), write_options)?;
115117
};
116118

117-
data.rewind()?;
119+
file.rewind()?;
118120

119121
let mut file_bytes = Vec::new();
120-
data.read_to_end(&mut file_bytes)?;
122+
file.read_to_end(&mut file_bytes)?;
121123

122124
// Write the tag in the appropriate place
123125
if let Some(range) = ape_tag_location {
@@ -131,9 +133,9 @@ where
131133
file_bytes.drain(header_ape_tag.1 .0 as usize..header_ape_tag.1 .1 as usize);
132134
}
133135

134-
data.rewind()?;
135-
data.set_len(0)?;
136-
data.write_all(&file_bytes)?;
136+
file.rewind()?;
137+
file.truncate(0)?;
138+
file.write_all(&file_bytes)?;
137139

138140
Ok(())
139141
}

src/error.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ pub enum ErrorKind {
6868
Io(std::io::Error),
6969
/// Failure to allocate enough memory
7070
Alloc(TryReserveError),
71+
/// This should **never** be encountered
72+
Infallible(std::convert::Infallible),
7173
}
7274

7375
/// The types of errors that can occur while interacting with ID3v2 tags
@@ -499,6 +501,14 @@ impl From<std::collections::TryReserveError> for LoftyError {
499501
}
500502
}
501503

504+
impl From<std::convert::Infallible> for LoftyError {
505+
fn from(input: std::convert::Infallible) -> Self {
506+
Self {
507+
kind: ErrorKind::Infallible(input),
508+
}
509+
}
510+
}
511+
502512
impl Display for LoftyError {
503513
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
504514
match self.kind {
@@ -540,6 +550,8 @@ impl Display for LoftyError {
540550
),
541551
ErrorKind::FileDecoding(ref file_decode_err) => write!(f, "{file_decode_err}"),
542552
ErrorKind::FileEncoding(ref file_encode_err) => write!(f, "{file_encode_err}"),
553+
554+
ErrorKind::Infallible(_) => write!(f, "A expected condition was not upheld"),
543555
}
544556
}
545557
}

src/file/audio_file.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use super::tagged_file::TaggedFile;
22
use crate::config::{ParseOptions, WriteOptions};
3-
use crate::error::Result;
3+
use crate::error::{LoftyError, Result};
44
use crate::tag::TagType;
55

6-
use std::fs::{File, OpenOptions};
6+
use crate::util::io::{FileLike, Length, Truncate};
7+
use std::fs::OpenOptions;
78
use std::io::{Read, Seek};
89
use std::path::Path;
910

@@ -77,7 +78,11 @@ pub trait AudioFile: Into<TaggedFile> {
7778
/// tagged_file.save_to(&mut file, WriteOptions::default())?;
7879
/// # Ok(()) }
7980
/// ```
80-
fn save_to(&self, file: &mut File, write_options: WriteOptions) -> Result<()>;
81+
fn save_to<F>(&self, file: &mut F, write_options: WriteOptions) -> Result<()>
82+
where
83+
F: FileLike,
84+
LoftyError: From<<F as Truncate>::Error>,
85+
LoftyError: From<<F as Length>::Error>;
8186

8287
/// Returns a reference to the file's properties
8388
fn properties(&self) -> &Self::Properties;

src/file/tagged_file.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use super::audio_file::AudioFile;
22
use super::file_type::FileType;
33
use crate::config::{ParseOptions, WriteOptions};
4-
use crate::error::Result;
4+
use crate::error::{LoftyError, Result};
55
use crate::properties::FileProperties;
66
use crate::tag::{Tag, TagExt, TagType};
77

8+
use crate::util::io::{FileLike, Length, Truncate};
89
use std::fs::File;
910
use std::io::{Read, Seek};
1011

@@ -423,7 +424,12 @@ impl AudioFile for TaggedFile {
423424
.read()
424425
}
425426

426-
fn save_to(&self, file: &mut File, write_options: WriteOptions) -> Result<()> {
427+
fn save_to<F>(&self, file: &mut F, write_options: WriteOptions) -> Result<()>
428+
where
429+
F: FileLike,
430+
LoftyError: From<<F as Truncate>::Error>,
431+
LoftyError: From<<F as Length>::Error>,
432+
{
427433
for tag in &self.tags {
428434
// TODO: This is a temporary solution. Ideally we should probe once and use
429435
// the format-specific writing to avoid these rewinds.
@@ -631,7 +637,12 @@ impl AudioFile for BoundTaggedFile {
631637
)
632638
}
633639

634-
fn save_to(&self, file: &mut File, write_options: WriteOptions) -> Result<()> {
640+
fn save_to<F>(&self, file: &mut F, write_options: WriteOptions) -> Result<()>
641+
where
642+
F: FileLike,
643+
LoftyError: From<<F as Truncate>::Error>,
644+
LoftyError: From<<F as Length>::Error>,
645+
{
635646
self.inner.save_to(file, write_options)
636647
}
637648

0 commit comments

Comments
 (0)