From 34c12fa06e76e002c1d0a2e5cf4ae903e19ce37c Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Sat, 25 Apr 2026 11:14:41 -0400 Subject: [PATCH] misc: Bump MSRV to 1.89.0 --- .github/workflows/ci.yml | 12 ++++++++++ CHANGELOG.md | 1 + Cargo.toml | 2 +- README.md | 2 +- lofty/src/ape/tag/mod.rs | 33 ++++++++++++++-------------- lofty/src/file/file_type.rs | 9 ++++---- lofty/src/flac/write.rs | 14 +++++------- lofty/src/id3/v1/tag.rs | 8 +++---- lofty/src/id3/v2/frame/header/mod.rs | 10 ++++----- lofty/src/id3/v2/tag.rs | 8 +++---- lofty/src/id3/v2/write/chunk_file.rs | 2 +- lofty/src/id3/v2/write/frame.rs | 24 ++++++++++---------- lofty/src/iff/aiff/tag.rs | 30 ++++++++++++------------- lofty/src/iff/chunk.rs | 2 +- lofty/src/iff/wav/tag/mod.rs | 8 +++---- lofty/src/lib.rs | 2 +- lofty/src/mp4/ilst/mod.rs | 16 +++++++------- lofty/src/mp4/ilst/read.rs | 19 ++++++++-------- lofty/src/mpeg/properties.rs | 25 +++++++++++---------- lofty/src/probe.rs | 14 ++++++------ lofty_attr/src/internal.rs | 13 +++++------ ogg_pager/src/paginate.rs | 12 +++++----- 22 files changed, 138 insertions(+), 128 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9de9f9cf0..0f5d618dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,18 @@ env: CARGO_TERM_COLOR: always jobs: + build_msrv: + name: Build MSRV + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@v1 + with: + toolchain: 1.89.0 + - run: | + cargo build --all-features --workspace --tests --examples + test: name: Tests runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1bfee80..01f318a15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **MP4**: `Mp4File::ftyp()` was moved to `Mp4Properties::ftyp()` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/650)) +- **MSRV**: Bumped to **1.89.0** ([PR](https://github.com/Serial-ATA/lofty-rs/pull/652)) ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 1f9bce94d..170b89688 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ [workspace.package] edition = "2024" -rust-version = "1.85" +rust-version = "1.89" repository = "https://github.com/Serial-ATA/lofty-rs" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index 55feef0d7..4df86c3ab 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/Serial-ATA/lofty-rs/ci.yml?branch=main&logo=github&style=for-the-badge)](https://github.com/Serial-ATA/lofty-rs/actions/workflows/ci.yml) [![Downloads](https://img.shields.io/crates/d/lofty?style=for-the-badge&logo=rust)](https://crates.io/crates/lofty) [![Version](https://img.shields.io/crates/v/lofty?style=for-the-badge&logo=rust)](https://crates.io/crates/lofty) -[![MSRV](https://img.shields.io/crates/msrv/lofty?style=for-the-badge&color=lightgray)](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/) +[![MSRV](https://img.shields.io/crates/msrv/lofty?style=for-the-badge&color=lightgray)](https://blog.rust-lang.org/2025/08/07/Rust-1.89.0/) [![Documentation](https://img.shields.io/badge/docs.rs-lofty-informational?style=for-the-badge&logo=read-the-docs)](https://docs.rs/lofty/) [![GitHub Sponsors](https://img.shields.io/github/sponsors/Serial-ATA?style=for-the-badge&logo=githubsponsors)](https://github.com/sponsors/Serial-ATA) diff --git a/lofty/src/ape/tag/mod.rs b/lofty/src/ape/tag/mod.rs index 6fad706d4..a1d940243 100644 --- a/lofty/src/ape/tag/mod.rs +++ b/lofty/src/ape/tag/mod.rs @@ -568,19 +568,19 @@ impl SplitTag for ApeTag { let item = std::mem::replace(item, ApeItem::EMPTY); // Multi-value string - if let Some(text) = item.value.text() { - if text.contains('\0') { - for value in text.split('\0') { - let item_value = match &item.value { - ItemValue::Text(_) => ItemValue::Text(value.to_string()), - ItemValue::Locator(_) => ItemValue::Locator(value.to_string()), - _ => unreachable!(), - }; - - tag.items.push(TagItem::new(k, item_value)) - } - return false; // Item consumed + if let Some(text) = item.value.text() + && text.contains('\0') + { + for value in text.split('\0') { + let item_value = match &item.value { + ItemValue::Text(_) => ItemValue::Text(value.to_string()), + ItemValue::Locator(_) => ItemValue::Locator(value.to_string()), + _ => unreachable!(), + }; + + tag.items.push(TagItem::new(k, item_value)) } + return false; // Item consumed } tag.items.push(TagItem::new(k, item.value)); @@ -604,12 +604,11 @@ impl MergeTag for SplitTagRemainder { } for pic in tag.pictures { - if let Some(key) = pic.pic_type.as_ape_key() { - if let Ok(item) = + if let Some(key) = pic.pic_type.as_ape_key() + && let Ok(item) = ApeItem::new(key.to_string(), ItemValue::Binary(pic.as_ape_bytes())) - { - merged.insert(item) - } + { + merged.insert(item) } } diff --git a/lofty/src/file/file_type.rs b/lofty/src/file/file_type.rs index a9d1dce76..af3cdc550 100644 --- a/lofty/src/file/file_type.rs +++ b/lofty/src/file/file_type.rs @@ -188,15 +188,14 @@ impl FileType { let ext = ext.as_ref().to_str()?.to_ascii_lowercase(); // Give custom resolvers priority - if unsafe { global_options().use_custom_resolvers } { - if let Some((ty, _)) = CUSTOM_RESOLVERS + if unsafe { global_options().use_custom_resolvers } + && let Some((ty, _)) = CUSTOM_RESOLVERS .lock() .ok()? .iter() .find(|(_, f)| f.extension() == Some(ext.as_str())) - { - return Some(Self::Custom(ty)); - } + { + return Some(Self::Custom(ty)); } // Also update `EXTENSIONS` above diff --git a/lofty/src/flac/write.rs b/lofty/src/flac/write.rs index f8a0e5861..518b7dbd5 100644 --- a/lofty/src/flac/write.rs +++ b/lofty/src/flac/write.rs @@ -140,16 +140,14 @@ where blocks.extend(metadata_blocks); - if will_write_padding { - if let Some(preferred_padding) = write_options.preferred_padding { - log::warn!("File is missing a PADDING block. Adding one"); + if will_write_padding && let Some(preferred_padding) = write_options.preferred_padding { + log::warn!("File is missing a PADDING block. Adding one"); - // `PADDING` always goes last - let mut padding_block = Block::new_padding(preferred_padding as usize)?; - padding_block.last = true; + // `PADDING` always goes last + let mut padding_block = Block::new_padding(preferred_padding as usize)?; + padding_block.last = true; - blocks.push(padding_block); - } + blocks.push(padding_block); } if let Some(block) = blocks.last_mut() { diff --git a/lofty/src/id3/v1/tag.rs b/lofty/src/id3/v1/tag.rs index c97476b61..4cd65d00e 100644 --- a/lofty/src/id3/v1/tag.rs +++ b/lofty/src/id3/v1/tag.rs @@ -326,10 +326,10 @@ impl SplitTag for Id3v1Tag { )) } - if let Some(genre_index) = self.genre.take() { - if let Some(genre) = GENRES.get(genre_index as usize) { - tag.insert_text(ItemKey::Genre, (*genre).to_string()); - } + if let Some(genre_index) = self.genre.take() + && let Some(genre) = GENRES.get(genre_index as usize) + { + tag.insert_text(ItemKey::Genre, (*genre).to_string()); } (SplitTagRemainder, tag) diff --git a/lofty/src/id3/v2/frame/header/mod.rs b/lofty/src/id3/v2/frame/header/mod.rs index 417f9f170..2b1fe12e5 100644 --- a/lofty/src/id3/v2/frame/header/mod.rs +++ b/lofty/src/id3/v2/frame/header/mod.rs @@ -197,11 +197,11 @@ impl TryFrom for FrameId<'_> { type Error = LoftyError; fn try_from(value: ItemKey) -> std::prelude::rust_2015::Result { - if let Some(mapped) = value.map_key(TagType::Id3v2) { - if mapped.len() == 4 { - Self::verify_id(mapped)?; - return Ok(Self::Valid(Cow::Borrowed(mapped))); - } + if let Some(mapped) = value.map_key(TagType::Id3v2) + && mapped.len() == 4 + { + Self::verify_id(mapped)?; + return Ok(Self::Valid(Cow::Borrowed(mapped))); } Err(Id3v2Error::new(Id3v2ErrorKind::UnsupportedFrameId(value)).into()) diff --git a/lofty/src/id3/v2/tag.rs b/lofty/src/id3/v2/tag.rs index cdad9c4e0..ed9af7c51 100644 --- a/lofty/src/id3/v2/tag.rs +++ b/lofty/src/id3/v2/tag.rs @@ -1302,10 +1302,10 @@ impl From for Tag { impl From for Id3v2Tag { fn from(mut input: Tag) -> Self { - if unsafe { global_options().preserve_format_specific_items } { - if let Some(companion) = input.companion_tag.take().and_then(CompanionTag::id3v2) { - return SplitTagRemainder(companion).merge_tag(input); - } + if unsafe { global_options().preserve_format_specific_items } + && let Some(companion) = input.companion_tag.take().and_then(CompanionTag::id3v2) + { + return SplitTagRemainder(companion).merge_tag(input); } SplitTagRemainder::default().merge_tag(input) diff --git a/lofty/src/id3/v2/write/chunk_file.rs b/lofty/src/id3/v2/write/chunk_file.rs index c6d24a396..06ee4b6f3 100644 --- a/lofty/src/id3/v2/write/chunk_file.rs +++ b/lofty/src/id3/v2/write/chunk_file.rs @@ -101,7 +101,7 @@ where // It is required an odd length chunk be padded with a 0 // The 0 isn't included in the chunk size, however - if tag.len() % 2 != 0 { + if !tag.len().is_multiple_of(2) { tag_bytes.write_u8(0)?; } diff --git a/lofty/src/id3/v2/write/frame.rs b/lofty/src/id3/v2/write/frame.rs index 0418ffbbf..38883f30f 100644 --- a/lofty/src/id3/v2/write/frame.rs +++ b/lofty/src/id3/v2/write/frame.rs @@ -333,19 +333,19 @@ where ); } - if let Some(mut len) = flags.data_length_indicator { - if len > 0 { - write_frame_header(writer, name, (value.len() + 1) as u32, flags, write_options)?; - if !write_options.use_id3v23 { - len = len.synch()?; - } - - writer.write_u32::(len)?; - writer.write_u8(method_symbol)?; - writer.write_all(value)?; - - return Ok(()); + if let Some(mut len) = flags.data_length_indicator + && len > 0 + { + write_frame_header(writer, name, (value.len() + 1) as u32, flags, write_options)?; + if !write_options.use_id3v23 { + len = len.synch()?; } + + writer.write_u32::(len)?; + writer.write_u8(method_symbol)?; + writer.write_all(value)?; + + return Ok(()); } Err(Id3v2Error::new(Id3v2ErrorKind::MissingDataLengthIndicator).into()) diff --git a/lofty/src/iff/aiff/tag.rs b/lofty/src/iff/aiff/tag.rs index 7e6b1229c..de6d4eec5 100644 --- a/lofty/src/iff/aiff/tag.rs +++ b/lofty/src/iff/aiff/tag.rs @@ -100,10 +100,10 @@ impl Accessor for AiffTextChunks { } fn comment(&self) -> Option> { - if let Some(ref anno) = self.annotations { - if !anno.is_empty() { - return anno.first().map(String::as_str).map(Cow::Borrowed); - } + if let Some(ref anno) = self.annotations + && !anno.is_empty() + { + return anno.first().map(String::as_str).map(Cow::Borrowed); } if let Some(ref comm) = self.comments { @@ -339,17 +339,17 @@ where fn create_text_chunks(tag: &mut AiffTextChunksRef<'_, T, AI>) -> Result> { fn write_chunk(writer: &mut Vec, key: &str, value: Option<&str>) { - if let Some(val) = value { - if let Ok(len) = u32::try_from(val.len()) { - writer.extend(key.as_bytes()); - writer.extend(len.to_be_bytes()); - writer.extend(val.as_bytes()); - - // AIFF only needs a terminator if the string is on an odd boundary, - // unlike RIFF, which makes use of both C-strings and even boundaries - if len % 2 != 0 { - writer.push(0); - } + if let Some(val) = value + && let Ok(len) = u32::try_from(val.len()) + { + writer.extend(key.as_bytes()); + writer.extend(len.to_be_bytes()); + writer.extend(val.as_bytes()); + + // AIFF only needs a terminator if the string is on an odd boundary, + // unlike RIFF, which makes use of both C-strings and even boundaries + if len % 2 != 0 { + writer.push(0); } } } diff --git a/lofty/src/iff/chunk.rs b/lofty/src/iff/chunk.rs index f2fe6f466..c4a012ab4 100644 --- a/lofty/src/iff/chunk.rs +++ b/lofty/src/iff/chunk.rs @@ -187,7 +187,7 @@ impl Chunks { // and it is NOT included in the chunk's size let mut padding_size = 1; - if current_chunk_size % 2 == 0 || self.remaining_size < padding_size { + if current_chunk_size.is_multiple_of(2) || self.remaining_size < padding_size { return Ok(()); } diff --git a/lofty/src/iff/wav/tag/mod.rs b/lofty/src/iff/wav/tag/mod.rs index 56ccd27fc..151256571 100644 --- a/lofty/src/iff/wav/tag/mod.rs +++ b/lofty/src/iff/wav/tag/mod.rs @@ -301,10 +301,10 @@ impl From for RiffInfoList { let mut riff_info = RiffInfoList::default(); for item in input.items { - if let ItemValue::Text(val) | ItemValue::Locator(val) = item.item_value { - if let Some(key) = item.item_key.map_key(TagType::RiffInfo) { - riff_info.items.push((key.to_string(), val)) - } + if let ItemValue::Text(val) | ItemValue::Locator(val) = item.item_value + && let Some(key) = item.item_key.map_key(TagType::RiffInfo) + { + riff_info.items.push((key.to_string(), val)) } } diff --git a/lofty/src/lib.rs b/lofty/src/lib.rs index 3fbafd3b8..1f8a2722d 100644 --- a/lofty/src/lib.rs +++ b/lofty/src/lib.rs @@ -1,7 +1,7 @@ //! [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/Serial-ATA/lofty-rs/ci.yml?branch=main&logo=github&style=for-the-badge)](https://github.com/Serial-ATA/lofty-rs/actions/workflows/ci.yml) //! [![Downloads](https://img.shields.io/crates/d/lofty?style=for-the-badge&logo=rust)](https://crates.io/crates/lofty) //! [![Version](https://img.shields.io/crates/v/lofty?style=for-the-badge&logo=rust)](https://crates.io/crates/lofty) -//! [![MSRV](https://img.shields.io/crates/msrv/lofty?style=for-the-badge&color=lightgray)](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/) +//! [![MSRV](https://img.shields.io/crates/msrv/lofty?style=for-the-badge&color=lightgray)](https://blog.rust-lang.org/2025/08/07/Rust-1.89.0/) //! //! Parse, convert, and write metadata to audio formats. //! diff --git a/lofty/src/mp4/ilst/mod.rs b/lofty/src/mp4/ilst/mod.rs index 731e80e3d..32fa82139 100644 --- a/lofty/src/mp4/ilst/mod.rs +++ b/lofty/src/mp4/ilst/mod.rs @@ -583,10 +583,10 @@ impl Accessor for Ilst { } fn date(&self) -> Option { - if let Some(atom) = self.get(&AtomIdent::Fourcc(*b"\xa9day")) { - if let Some(AtomData::UTF8(text)) = atom.data().next() { - return try_parse_timestamp(text); - } + if let Some(atom) = self.get(&AtomIdent::Fourcc(*b"\xa9day")) + && let Some(AtomData::UTF8(text)) = atom.data().next() + { + return try_parse_timestamp(text); } None @@ -885,10 +885,10 @@ impl From for Tag { impl From for Ilst { fn from(mut input: Tag) -> Self { - if unsafe { global_options().preserve_format_specific_items } { - if let Some(companion) = input.companion_tag.take().and_then(CompanionTag::ilst) { - return SplitTagRemainder(companion).merge_tag(input); - } + if unsafe { global_options().preserve_format_specific_items } + && let Some(companion) = input.companion_tag.take().and_then(CompanionTag::ilst) + { + return SplitTagRemainder(companion).merge_tag(input); } SplitTagRemainder::default().merge_tag(input) diff --git a/lofty/src/mp4/ilst/read.rs b/lofty/src/mp4/ilst/read.rs index 9ef297b88..910c24c02 100644 --- a/lofty/src/mp4/ilst/read.rs +++ b/lofty/src/mp4/ilst/read.rs @@ -132,17 +132,16 @@ where b"cpil" | b"hdvd" | b"pcst" | b"pgap" | b"shwm" => { if let Some(atom_data) = parse_data_inner(&mut ilst_reader, parsing_mode, &atom)? + && let Some((_, content)) = atom_data.first() { - if let Some((_, content)) = atom_data.first() { - // Any size integer is technically valid, we'll correct it on write. - let is_true = content.iter().any(|&b| b != 0); - let data = AtomData::Bool(is_true); - - tag.atoms.push(Atom { - ident: AtomIdent::Fourcc(*fourcc), - data: AtomDataStorage::Single(data), - }) - } + // Any size integer is technically valid, we'll correct it on write. + let is_true = content.iter().any(|&b| b != 0); + let data = AtomData::Bool(is_true); + + tag.atoms.push(Atom { + ident: AtomIdent::Fourcc(*fourcc), + data: AtomDataStorage::Single(data), + }) } continue; diff --git a/lofty/src/mpeg/properties.rs b/lofty/src/mpeg/properties.rs index b6e0234ee..288c79098 100644 --- a/lofty/src/mpeg/properties.rs +++ b/lofty/src/mpeg/properties.rs @@ -148,23 +148,24 @@ where 2 }; - if let Some(vbr_header) = vbr_header { - if first_frame_header.sample_rate > 0 && vbr_header.is_valid() { - log::debug!("MPEG: Valid VBR header; using it to calculate duration"); + if let Some(vbr_header) = vbr_header + && first_frame_header.sample_rate > 0 + && vbr_header.is_valid() + { + log::debug!("MPEG: Valid VBR header; using it to calculate duration"); - let sample_rate = u64::from(first_frame_header.sample_rate); - let samples_per_frame = u64::from(first_frame_header.samples); + let sample_rate = u64::from(first_frame_header.sample_rate); + let samples_per_frame = u64::from(first_frame_header.samples); - let total_frames = u64::from(vbr_header.frames); + let total_frames = u64::from(vbr_header.frames); - let length = (samples_per_frame * 1000 * total_frames).div_round(sample_rate); + let length = (samples_per_frame * 1000 * total_frames).div_round(sample_rate); - properties.duration = Duration::from_millis(length); - properties.overall_bitrate = ((file_length * 8) / length) as u32; - properties.audio_bitrate = ((u64::from(vbr_header.size) * 8) / length) as u32; + properties.duration = Duration::from_millis(length); + properties.overall_bitrate = ((file_length * 8) / length) as u32; + properties.audio_bitrate = ((u64::from(vbr_header.size) * 8) / length) as u32; - return Ok(()); - } + return Ok(()); } // Nothing more we can do diff --git a/lofty/src/probe.rs b/lofty/src/probe.rs index 48adb3045..5dd3ec8fa 100644 --- a/lofty/src/probe.rs +++ b/lofty/src/probe.rs @@ -335,13 +335,13 @@ impl Probe { self.inner.seek(SeekFrom::Start(starting_position))?; // Give custom resolvers priority - if unsafe { global_options().use_custom_resolvers } { - if let Ok(lock) = CUSTOM_RESOLVERS.lock() { - #[allow(clippy::significant_drop_in_scrutinee)] - for (_, resolve) in lock.iter() { - if let ret @ Some(_) = resolve.guess(&buf[..buf_len]) { - return Ok(ret); - } + if unsafe { global_options().use_custom_resolvers } + && let Ok(lock) = CUSTOM_RESOLVERS.lock() + { + #[allow(clippy::significant_drop_in_scrutinee)] + for (_, resolve) in lock.iter() { + if let ret @ Some(_) = resolve.guess(&buf[..buf_len]) { + return Ok(ret); } } } diff --git a/lofty_attr/src/internal.rs b/lofty_attr/src/internal.rs index fe79f3aaa..03521d659 100644 --- a/lofty_attr/src/internal.rs +++ b/lofty_attr/src/internal.rs @@ -17,16 +17,15 @@ pub(crate) fn opt_internal_file_type( const ID3V2_STRIPPABLE: [&str; 2] = ["Flac", "Ape"]; let stripped = struct_name.strip_suffix("File"); - if let Some(prefix) = stripped { - if let Some(pos) = LOFTY_FILE_TYPES + if let Some(prefix) = stripped + && let Some(pos) = LOFTY_FILE_TYPES .iter() .position(|p| p.eq_ignore_ascii_case(prefix)) - { - let file_ty = LOFTY_FILE_TYPES[pos]; - let tt = file_ty.parse::().unwrap(); + { + let file_ty = LOFTY_FILE_TYPES[pos]; + let tt = file_ty.parse::().unwrap(); - return Some((tt, ID3V2_STRIPPABLE.contains(&file_ty))); - } + return Some((tt, ID3V2_STRIPPABLE.contains(&file_ty))); } None diff --git a/ogg_pager/src/paginate.rs b/ogg_pager/src/paginate.rs index 65f2a1b17..4a6f35581 100644 --- a/ogg_pager/src/paginate.rs +++ b/ogg_pager/src/paginate.rs @@ -146,10 +146,10 @@ where paginate_packet(&mut ctx, packet)?; } - if flags & CONTAINS_LAST_PAGE_OF_BITSTREAM == 0x04 { - if let Some(last) = ctx.pages.last_mut() { - last.header.header_type_flag |= CONTAINS_LAST_PAGE_OF_BITSTREAM; - } + if flags & CONTAINS_LAST_PAGE_OF_BITSTREAM == 0x04 + && let Some(last) = ctx.pages.last_mut() + { + last.header.header_type_flag |= CONTAINS_LAST_PAGE_OF_BITSTREAM; } Ok(ctx.pages) @@ -199,7 +199,9 @@ fn paginate_packet(ctx: &mut PaginateContext, packet: &[u8]) -> Result<()> { // From : // "Note also that a 'nil' (zero length) packet is not an error; it consists of nothing more than a lacing value of zero in the header." if ctx.current_packet_len != 0 - && ctx.current_packet_len % (255 * MAX_WRITTEN_SEGMENT_COUNT) == 0 + && ctx + .current_packet_len + .is_multiple_of(255 * MAX_WRITTEN_SEGMENT_COUNT) { ctx.flags.packet_finished_on_page = true; let mut nil_content = Vec::new();