fix: tackle lots of clippy lints, along with removing outputstream from player struct

This commit is contained in:
talwat 2025-02-21 21:44:32 +01:00
parent a720e9d2cf
commit ece88de1ae
10 changed files with 79 additions and 88 deletions

2
Cargo.lock generated
View File

@ -1453,7 +1453,7 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "lowfi"
version = "1.6.0"
version = "1.6.1"
dependencies = [
"Inflector",
"arc-swap",

View File

@ -1,6 +1,6 @@
[package]
name = "lowfi"
version = "1.6.0"
version = "1.6.1"
edition = "2021"
description = "An extremely simple lofi player."
license = "MIT"

View File

@ -1,44 +1,6 @@
//! An extremely simple lofi player.
#![warn(clippy::all, clippy::restriction, clippy::pedantic, clippy::nursery)]
#![allow(
clippy::single_call_fn,
clippy::struct_excessive_bools,
clippy::implicit_return,
clippy::question_mark_used,
clippy::shadow_reuse,
clippy::indexing_slicing,
clippy::arithmetic_side_effects,
clippy::std_instead_of_core,
clippy::print_stdout,
clippy::float_arithmetic,
clippy::integer_division_remainder_used,
clippy::used_underscore_binding,
clippy::print_stderr,
clippy::semicolon_outside_block,
clippy::non_send_fields_in_send_ty,
clippy::non_ascii_literal,
clippy::let_underscore_untyped,
clippy::let_underscore_must_use,
clippy::shadow_unrelated,
clippy::std_instead_of_alloc,
clippy::partial_pub_fields,
clippy::unseparated_literal_suffix,
clippy::self_named_module_files,
// TODO: Disallow these lints later.
clippy::unwrap_used,
clippy::pattern_type_mismatch,
clippy::tuple_array_conversions,
clippy::as_conversions,
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::wildcard_enum_match_arm,
clippy::integer_division,
clippy::cast_sign_loss,
clippy::cast_lossless,
clippy::arbitrary_source_item_ordering,
clippy::unused_trait_names
)]
#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
use clap::{Parser, Subcommand};
@ -52,6 +14,10 @@ mod scrape;
/// An extremely simple lofi player.
#[derive(Parser)]
#[command(about, version)]
#[allow(
clippy::struct_excessive_bools,
reason = "señor clippy, i assure you this is not a state machine"
)]
struct Args {
/// Whether to use an alternate terminal screen.
#[clap(long, short)]

View File

@ -23,7 +23,7 @@ impl PersistentVolume {
/// Retrieves the config directory.
async fn config() -> eyre::Result<PathBuf> {
let config = dirs::config_dir()
.ok_or(eyre!("Couldn't find config directory"))?
.ok_or_else(|| eyre!("Couldn't find config directory"))?
.join(PathBuf::from("lowfi"));
if !config.exists() {
@ -35,7 +35,7 @@ impl PersistentVolume {
/// Returns the volume as a float from 0 to 1.
pub fn float(self) -> f32 {
self.inner as f32 / 100.0
f32::from(self.inner) / 100.0
}
/// Loads the [`PersistentVolume`] from [`dirs::config_dir()`].
@ -64,17 +64,38 @@ impl PersistentVolume {
let config = Self::config().await?;
let path = config.join(PathBuf::from("volume.txt"));
fs::write(path, ((volume * 100.0).abs().round() as u16).to_string()).await?;
#[expect(
clippy::as_conversions,
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
reason = "already rounded & absolute, therefore this should be safe"
)]
let percentage = (volume * 100.0).abs().round() as u16;
fs::write(path, percentage.to_string()).await?;
Ok(())
}
}
/// Wrapper around [`rodio::OutputStream`] to implement [Send], currently unsafely.
///
/// This is more of a temporary solution until cpal implements [Send] on it's output stream.
pub struct SendableOutputStream(pub rodio::OutputStream);
// SAFETY: This is necessary because [OutputStream] does not implement [Send],
// due to some limitation with Android's Audio API.
// I'm pretty sure nobody will use lowfi with android, so this is safe.
#[expect(clippy::non_send_fields_in_send_ty, reason = "yes")]
unsafe impl Send for SendableOutputStream {}
/// Initializes the audio server, and then safely stops
/// it when the frontend quits.
pub async fn play(args: Args) -> eyre::Result<()> {
// Actually initializes the player.
let player = Arc::new(Player::new(&args).await?);
// Stream kept here in the master thread to keep it alive.
let (player, stream) = Player::new(&args).await?;
let player = Arc::new(player);
// Initialize the UI, as well as the internal communication channel.
let (tx, rx) = mpsc::channel(8);
@ -90,6 +111,7 @@ pub async fn play(args: Args) -> eyre::Result<()> {
PersistentVolume::save(player.sink.volume()).await?;
player.sink.stop();
ui.abort();
drop(stream.0);
Ok(())
}

View File

@ -2,11 +2,10 @@
//! This also has the code for the underlying
//! audio server which adds new tracks.
use std::{collections::VecDeque, ffi::CString, sync::Arc, time::Duration};
use std::{collections::VecDeque, sync::Arc, time::Duration};
use arc_swap::ArcSwapOption;
use downloader::Downloader;
use libc::freopen;
use reqwest::Client;
use rodio::{OutputStream, OutputStreamHandle, Sink};
use tokio::{
@ -23,7 +22,7 @@ use tokio::{
use mpris_server::{PlaybackStatus, PlayerInterface, Property};
use crate::{
play::PersistentVolume,
play::{PersistentVolume, SendableOutputStream},
tracks::{self, list::List},
Args,
};
@ -53,6 +52,7 @@ pub enum Messages {
Init,
/// Unpause the [Sink].
#[allow(dead_code, reason = "this code may not be dead depending on features")]
Play,
/// Pauses the [Sink].
@ -109,20 +109,8 @@ pub struct Player {
/// playback, is for now unused and is here just to keep it
/// alive so the playback can function properly.
_handle: OutputStreamHandle,
/// The [`OutputStream`], which is just here to keep the playback
/// alive and functioning.
_stream: OutputStream,
}
// SAFETY: This is necessary because [OutputStream] does not implement [Send],
// due to some limitation with Android's Audio API.
// I'm pretty sure nobody will use lowfi with android, so this is safe.
unsafe impl Send for Player {}
// SAFETY: See implementation for [Send].
unsafe impl Sync for Player {}
impl Player {
/// This gets the output stream while also shutting up alsa with [libc].
/// Uses raw libc calls, and therefore is functional only on Linux.
@ -182,7 +170,7 @@ impl Player {
/// Initializes the entire player, including audio devices & sink.
///
/// This also will load the track list & persistent volume.
pub async fn new(args: &Args) -> eyre::Result<Self> {
pub async fn new(args: &Args) -> eyre::Result<(Self, SendableOutputStream)> {
// Load the volume file.
let volume = PersistentVolume::load().await?;
@ -199,7 +187,7 @@ impl Player {
// If we're not on Linux, then there's no problem.
#[cfg(not(target_os = "linux"))]
let (_stream, handle) = OutputStream::try_default()?;
let (stream, handle) = OutputStream::try_default()?;
let sink = Sink::try_new(&handle)?;
if args.paused {
@ -223,10 +211,9 @@ impl Player {
volume,
list,
_handle: handle,
_stream,
};
Ok(player)
Ok((player, SendableOutputStream(stream)))
}
/// This will play the next track, as well as refilling the buffer in the background.

View File

@ -1,7 +1,15 @@
//! The module which manages all user interface, including inputs.
#![allow(
clippy::as_conversions,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
reason = "the ui is full of these because of various layout & positioning aspects, and for a simple music player making all casts safe is not worth the effort"
)]
use std::{
fmt::Write,
fmt::Write as _,
io::{stdout, Stdout},
sync::{
atomic::{AtomicUsize, Ordering},
@ -15,7 +23,7 @@ use crate::Args;
use crossterm::{
cursor::{Hide, MoveTo, MoveToColumn, MoveUp, Show},
event::{KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags},
style::{Print, Stylize},
style::{Print, Stylize as _},
terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen},
};
@ -50,7 +58,7 @@ lazy_static! {
/// Sets the volume timer to one, effectively flashing the audio display in lowfi's UI.
///
/// The amount of frames the audio display is visible for is determined by [AUDIO_BAR_DURATION].
/// The amount of frames the audio display is visible for is determined by [`AUDIO_BAR_DURATION`].
pub fn flash_audio() {
VOLUME_TIMER.store(1, Ordering::Relaxed);
}
@ -96,7 +104,7 @@ impl Window {
/// Actually draws the window, with each element in `content` being on a new line.
pub fn draw(&mut self, content: Vec<String>) -> eyre::Result<()> {
let len = content.len() as u16;
let len: u16 = content.len().try_into()?;
// Note that this will have a trailing newline, which we use later.
let menu: String = content.into_iter().fold(String::new(), |mut output, x| {

View File

@ -1,10 +1,10 @@
//! Various different individual components that
//! appear in lowfi's UI, like the progress bar.
use std::{ops::Deref, sync::Arc, time::Duration};
use std::{ops::Deref as _, sync::Arc, time::Duration};
use crossterm::style::Stylize;
use unicode_segmentation::UnicodeSegmentation;
use crossterm::style::Stylize as _;
use unicode_segmentation::UnicodeSegmentation as _;
use crate::{player::Player, tracks::Info};

View File

@ -2,7 +2,7 @@
//! using [`crossterm`].
use crossterm::event::{self, EventStream, KeyCode, KeyEventKind, KeyModifiers};
use futures::{FutureExt, StreamExt};
use futures::{FutureExt as _, StreamExt as _};
use tokio::sync::mpsc::Sender;
use crate::player::{ui, Messages};

View File

@ -5,9 +5,10 @@
use std::{io::Cursor, time::Duration};
use bytes::Bytes;
use inflector::Inflector;
use rodio::{Decoder, Source};
use unicode_width::UnicodeWidthStr;
use eyre::OptionExt as _;
use inflector::Inflector as _;
use rodio::{Decoder, Source as _};
use unicode_width::UnicodeWidthStr as _;
use url::form_urlencoded;
pub mod list;
@ -36,6 +37,10 @@ pub struct Info {
impl Info {
/// Decodes a URL string into normal UTF-8.
fn decode_url(text: &str) -> String {
#[expect(
clippy::tuple_array_conversions,
reason = "the tuple contains smart pointers, so it's not really practical to use `into()`"
)]
form_urlencoded::parse(text.as_bytes())
.map(|(key, val)| [key, val].concat())
.collect()
@ -44,11 +49,13 @@ impl Info {
/// Formats a name with [Inflector].
/// This will also strip the first few numbers that are
/// usually present on most lofi tracks.
fn format_name(name: &str) -> String {
let split = name.split('/').last().unwrap();
fn format_name(name: &str) -> eyre::Result<String> {
let split = name
.split('/')
.last()
.ok_or_eyre("split is never supposed to return nothing")?;
let stripped = split.strip_suffix(".mp3").unwrap_or(split);
let formatted = Self::decode_url(stripped)
.to_lowercase()
.to_title_case()
@ -76,28 +83,28 @@ impl Info {
// If the entire name of the track is a number, then just return it.
if skip == formatted.len() {
formatted
Ok(formatted)
} else {
#[expect(
clippy::string_slice,
reason = "We've already checked before that the bound is at an ASCII digit."
)]
String::from(&formatted[skip..])
Ok(String::from(&formatted[skip..]))
}
}
/// Creates a new [`TrackInfo`] from a possibly raw name & decoded track data.
pub fn new(name: TrackName, decoded: &DecodedData) -> Self {
pub fn new(name: TrackName, decoded: &DecodedData) -> eyre::Result<Self> {
let name = match name {
TrackName::Raw(raw) => Self::format_name(&raw),
TrackName::Raw(raw) => Self::format_name(&raw)?,
TrackName::Formatted(formatted) => formatted,
};
Self {
Ok(Self {
duration: decoded.total_duration(),
width: name.width(),
name,
}
})
}
}
@ -116,7 +123,7 @@ impl Decoded {
/// This is equivalent to [`Track::decode`].
pub fn new(track: Track) -> eyre::Result<Self> {
let data = Decoder::new(Cursor::new(track.data))?;
let info = Info::new(track.name, &data);
let info = Info::new(track.name, &data)?;
Ok(Self { info, data })
}

View File

@ -2,8 +2,8 @@
//! as well as obtaining track names & downloading the raw mp3 data.
use bytes::Bytes;
use eyre::OptionExt;
use rand::Rng;
use eyre::OptionExt as _;
use rand::Rng as _;
use reqwest::Client;
use tokio::fs;
@ -15,6 +15,7 @@ use super::Track;
#[derive(Clone)]
pub struct List {
/// The "name" of the list, usually derived from a filename.
#[allow(dead_code, reason = "this code may not be dead depending on features")]
pub name: String,
/// Just the raw file, but seperated by `/n` (newlines).
@ -90,7 +91,7 @@ impl List {
if let Some(arg) = tracks {
// Check if the track is in ~/.local/share/lowfi, in which case we'll load that.
let name = dirs::data_dir()
.unwrap()
.ok_or_eyre("data directory not found, are you *really* running this on wasm?")?
.join("lowfi")
.join(format!("{arg}.txt"));