mirror of
https://github.com/talwat/lowfi
synced 2025-03-12 16:12:22 +00:00
fix: tackle lots of clippy lints, along with removing outputstream from player struct
This commit is contained in:
parent
a720e9d2cf
commit
ece88de1ae
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1453,7 +1453,7 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "lowfi"
|
||||
version = "1.6.0"
|
||||
version = "1.6.1"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"arc-swap",
|
||||
|
@ -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"
|
||||
|
44
src/main.rs
44
src/main.rs
@ -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)]
|
||||
|
30
src/play.rs
30
src/play.rs
@ -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(())
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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| {
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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};
|
||||
|
@ -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 })
|
||||
}
|
||||
|
@ -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"));
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user