chore: configure clippy & fix lints

This commit is contained in:
Tal 2025-12-04 20:53:19 +01:00
parent 1c8c788d76
commit a87a8cc59e
19 changed files with 163 additions and 120 deletions

View File

@ -60,3 +60,17 @@ indicatif = { version = "0.18.0", optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
libc = "0.2.167"
[lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
unwrap_in_result = "warn"
missing_docs_in_private_items = "warn"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
must_use_candidate = "allow"
cast_precision_loss = "allow"
cast_sign_loss = "allow"
cast_possible_truncation = "allow"

View File

@ -25,7 +25,7 @@ pub fn silent_get_output_stream() -> eyre::Result<rodio::OutputStream, crate::Er
// SAFETY: Simple enough to be impossible to fail. Hopefully.
unsafe {
freopen(null.as_ptr(), mode.as_ptr(), stderr);
}
};
// Make the OutputStream while stderr is still redirected to /dev/null.
let stream = OutputStreamBuilder::open_default_stream()?;
@ -36,7 +36,7 @@ pub fn silent_get_output_stream() -> eyre::Result<rodio::OutputStream, crate::Er
// SAFETY: See the first call to `freopen`.
unsafe {
freopen(tty.as_ptr(), mode.as_ptr(), stderr);
}
};
Ok(stream)
}

View File

@ -23,7 +23,7 @@ impl Handle {
let notify = Arc::new(Notify::new());
Self {
task: task::spawn(Self::waiter(sink, tx, notify.clone())),
task: task::spawn(Self::waiter(sink, tx, Arc::clone(&notify))),
notify,
}
}
@ -40,7 +40,7 @@ impl Handle {
time::sleep(Duration::from_millis(8)).await;
}
if let Err(_) = tx.try_send(crate::Message::Next) {
if tx.try_send(crate::Message::Next).is_err() {
break;
};
}

View File

@ -46,7 +46,7 @@ impl Bookmarks {
if x.is_empty() {
None
} else {
Some(x.to_string())
Some(x.to_owned())
}
})
.collect();
@ -64,7 +64,7 @@ impl Bookmarks {
/// Bookmarks a given track with a full path and optional custom name.
///
/// Returns whether the track is now bookmarked, or not.
pub async fn bookmark(&mut self, track: &tracks::Info) -> Result<bool> {
pub fn bookmark(&mut self, track: &tracks::Info) -> Result<bool> {
let entry = track.to_entry();
let idx = self.entries.iter().position(|x| **x == entry);

View File

@ -15,6 +15,8 @@ static LOADING: AtomicBool = AtomicBool::new(false);
pub(crate) static PROGRESS: AtomicU8 = AtomicU8::new(0);
pub type Progress = &'static AtomicU8;
/// The downloader, which has all of the state necessary
/// to download tracks and add them to the queue.
pub struct Downloader {
queue: Sender<tracks::Queued>,
tx: Sender<crate::Message>,
@ -24,7 +26,10 @@ pub struct Downloader {
}
impl Downloader {
pub async fn init(size: usize, tracks: tracks::List, tx: Sender<crate::Message>) -> Handle {
/// Initializes the downloader with a track list.
///
/// `tx` specifies the [`Sender`] to be notified with [`crate::Message::Loaded`].
pub fn init(size: usize, tracks: tracks::List, tx: Sender<crate::Message>) -> Handle {
let client = Client::new();
let (qtx, qrx) = mpsc::channel(size - 1);
@ -64,25 +69,30 @@ impl Downloader {
}
}
}
/// Downloader handle, responsible for managing
/// the downloader task and internal buffer.
pub struct Handle {
queue: Receiver<tracks::Queued>,
handle: JoinHandle<crate::Result<()>>,
}
/// The output when a track is requested from the downloader.
pub enum Output {
Loading(Option<Progress>),
Queued(tracks::Queued),
}
impl Handle {
pub async fn track(&mut self) -> Output {
match self.queue.try_recv() {
Ok(queued) => Output::Queued(queued),
Err(_) => {
/// Gets either a queued track, or a progress report,
/// depending on the state of the internal download buffer.
#[rustfmt::skip]
pub fn track(&mut self) -> Output {
self.queue.try_recv().map_or_else(|_| {
LOADING.store(true, atomic::Ordering::Relaxed);
Output::Loading(Some(&PROGRESS))
}
}
}, Output::Queued,
)
}
}

View File

@ -1,6 +1,4 @@
//! An extremely simple lofi player.
#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
pub mod error;
use std::path::PathBuf;
@ -98,23 +96,21 @@ pub fn data_dir() -> crate::Result<PathBuf> {
async fn main() -> eyre::Result<()> {
let args = Args::parse();
if let Some(command) = args.command {
#[cfg(feature = "scrape")]
if let Some(command) = &args.command {
match command {
#[cfg(feature = "scrape")]
Commands::Scrape { source } => match source {
Source::Archive => scrapers::archive::scrape().await?,
Source::Lofigirl => scrapers::lofigirl::scrape().await?,
Source::Chillhop => scrapers::chillhop::scrape().await?,
},
}
} else {
let player = Player::init(args).await?;
let environment = player.environment();
let result = player.run().await;
}
environment.cleanup(result.is_ok())?;
result?;
};
let player = Player::init(args).await?;
let environment = player.environment();
let result = player.run().await;
Ok(())
environment.cleanup(result.is_ok())?;
Ok(result?)
}

View File

@ -23,13 +23,13 @@ pub enum Current {
impl Default for Current {
fn default() -> Self {
Current::Loading(None)
Self::Loading(None)
}
}
impl Current {
pub fn loading(&self) -> bool {
return matches!(self, Current::Loading(_));
pub const fn loading(&self) -> bool {
matches!(self, Self::Loading(_))
}
}
@ -53,25 +53,25 @@ impl Drop for Player {
}
impl Player {
pub fn environment(&self) -> ui::Environment {
pub const fn environment(&self) -> ui::Environment {
self.ui.environment
}
pub async fn set_current(&mut self, current: Current) -> crate::Result<()> {
pub fn set_current(&mut self, current: Current) -> crate::Result<()> {
self.current = current.clone();
self.update(ui::Update::Track(current)).await?;
self.update(ui::Update::Track(current))?;
let Current::Track(track) = &self.current else {
return Ok(());
};
let bookmarked = self.bookmarks.bookmarked(&track);
self.update(ui::Update::Bookmarked(bookmarked)).await?;
let bookmarked = self.bookmarks.bookmarked(track);
self.update(ui::Update::Bookmarked(bookmarked))?;
Ok(())
}
pub async fn update(&mut self, update: ui::Update) -> crate::Result<()> {
pub fn update(&mut self, update: ui::Update) -> crate::Result<()> {
self.broadcast.send(update)?;
Ok(())
}
@ -87,21 +87,19 @@ impl Player {
let (tx, rx) = mpsc::channel(8);
tx.send(Message::Init).await?;
let (utx, urx) = broadcast::channel(8);
let current = Current::Loading(None);
let list = List::load(args.track_list.as_ref()).await?;
let state =
ui::State::initial(sink.clone(), args.width, current.clone(), list.name.clone());
let state = ui::State::initial(Arc::clone(&sink), args.width, list.name.clone());
let volume = PersistentVolume::load().await?;
sink.set_volume(volume.float());
Ok(Self {
ui: ui::Handle::init(tx.clone(), urx, state, &args).await?,
downloader: Downloader::init(args.buffer_size as usize, list, tx.clone()).await,
waiter: waiter::Handle::new(sink.clone(), tx.clone()),
downloader: Downloader::init(args.buffer_size as usize, list, tx.clone()),
waiter: waiter::Handle::new(Arc::clone(&sink), tx.clone()),
bookmarks: Bookmarks::load().await?,
current,
current: Current::default(),
broadcast: utx,
rx,
sink,
@ -112,15 +110,15 @@ impl Player {
pub async fn close(&self) -> crate::Result<()> {
self.bookmarks.save().await?;
PersistentVolume::save(self.sink.volume() as f32).await?;
PersistentVolume::save(self.sink.volume()).await?;
Ok(())
}
pub async fn play(&mut self, queued: tracks::Queued) -> crate::Result<()> {
pub fn play(&mut self, queued: tracks::Queued) -> crate::Result<()> {
let decoded = queued.decode()?;
self.sink.append(decoded.data);
self.set_current(Current::Track(decoded.info)).await?;
self.set_current(Current::Track(decoded.info))?;
self.waiter.notify();
Ok(())
@ -135,12 +133,12 @@ impl Player {
}
self.sink.stop();
match self.downloader.track().await {
match self.downloader.track() {
download::Output::Loading(progress) => {
self.set_current(Current::Loading(progress)).await?;
self.set_current(Current::Loading(progress))?;
}
download::Output::Queued(queued) => {
self.play(queued).await?;
self.play(queued)?;
}
};
}
@ -160,19 +158,19 @@ impl Player {
Message::ChangeVolume(change) => {
self.sink
.set_volume((self.sink.volume() + change).clamp(0.0, 1.0));
self.update(ui::Update::Volume).await?;
self.update(ui::Update::Volume)?;
}
Message::SetVolume(set) => {
self.sink.set_volume(set.clamp(0.0, 1.0));
self.update(ui::Update::Volume).await?;
self.update(ui::Update::Volume)?;
}
Message::Bookmark => {
let Current::Track(current) = &self.current else {
continue;
};
let bookmarked = self.bookmarks.bookmark(current).await?;
self.update(ui::Update::Bookmarked(bookmarked)).await?;
let bookmarked = self.bookmarks.bookmark(current)?;
self.update(ui::Update::Bookmarked(bookmarked))?;
}
Message::Quit => break,
}

View File

@ -11,8 +11,8 @@ mod bookmark {
}
}
#[tokio::test]
async fn toggle_and_check() {
#[test]
fn toggle_and_check() {
let mut bm = Bookmarks { entries: vec![] };
let info = test_info("p.mp3", "Nice Track");
@ -20,37 +20,37 @@ mod bookmark {
assert!(!bm.bookmarked(&info));
// bookmark it
let added = bm.bookmark(&info).await.unwrap();
let added = bm.bookmark(&info).unwrap();
assert!(added);
assert!(bm.bookmarked(&info));
// un-bookmark it
let removed = bm.bookmark(&info).await.unwrap();
let removed = bm.bookmark(&info).unwrap();
assert!(!removed);
assert!(!bm.bookmarked(&info));
}
#[tokio::test]
async fn multiple_bookmarks() {
#[test]
fn multiple_bookmarks() {
let mut bm = Bookmarks { entries: vec![] };
let info1 = test_info("track1.mp3", "Track One");
let info2 = test_info("track2.mp3", "Track Two");
bm.bookmark(&info1).await.unwrap();
bm.bookmark(&info2).await.unwrap();
bm.bookmark(&info1).unwrap();
bm.bookmark(&info2).unwrap();
assert!(bm.bookmarked(&info1));
assert!(bm.bookmarked(&info2));
assert_eq!(bm.entries.len(), 2);
}
#[tokio::test]
async fn duplicate_bookmark_removes() {
#[test]
fn duplicate_bookmark_removes() {
let mut bm = Bookmarks { entries: vec![] };
let info = test_info("x.mp3", "X");
bm.bookmark(&info).await.unwrap();
let is_added = bm.bookmark(&info).await.unwrap();
bm.bookmark(&info).unwrap();
let is_added = bm.bookmark(&info).unwrap();
assert!(!is_added);
assert!(bm.entries.is_empty());

View File

@ -79,7 +79,7 @@ mod window {
#[test]
fn simple() {
let mut w = Window::new(3, false);
let w = Window::new(3, false);
let (render, height) = w.render(vec![String::from("abc")], false, true).unwrap();
const MIDDLE: &str = "─────";
@ -89,7 +89,7 @@ mod window {
#[test]
fn spaced() {
let mut w = Window::new(3, false);
let w = Window::new(3, false);
let (render, height) = w
.render(
vec![String::from("abc"), String::from(" b"), String::from("c")],
@ -137,7 +137,7 @@ mod interface {
#[test]
fn loading() {
let sink = Arc::new(rodio::Sink::new().0);
let mut state = State::initial(sink, 3, Current::Loading(None), String::from("test"));
let mut state = State::initial(sink, 3, String::from("test"));
let menu = interface::menu(&mut state, Params::default());
assert_eq!(menu[0], "loading ");
@ -157,7 +157,7 @@ mod interface {
fn volume() {
let sink = Arc::new(rodio::Sink::new().0);
sink.set_volume(0.5);
let mut state = State::initial(sink, 3, Current::Loading(None), String::from("test"));
let mut state = State::initial(sink, 3, String::from("test"));
state.timer = Some(Instant::now());
let menu = interface::menu(&mut state, Params::default());
@ -179,12 +179,9 @@ mod interface {
fn progress() {
let sink = Arc::new(rodio::Sink::new().0);
PROGRESS.store(50, std::sync::atomic::Ordering::Relaxed);
let mut state = State::initial(
sink,
3,
Current::Loading(Some(&PROGRESS)),
String::from("test"),
);
let mut state = State::initial(sink, 3, String::from("test"));
state.current = Current::Loading(Some(&PROGRESS));
let menu = interface::menu(&mut state, Params::default());
assert_eq!(menu[0], format!("loading {} ", "50%".bold()));
@ -210,8 +207,8 @@ mod interface {
duration: Some(Duration::from_secs(8)),
};
let current = Current::Track(track.clone());
let mut state = State::initial(sink, 3, current, String::from("test"));
let mut state = State::initial(sink, 3, String::from("test"));
state.current = Current::Track(track.clone());
let menu = interface::menu(&mut state, Params::default());
assert_eq!(

View File

@ -2,24 +2,18 @@
//! of tracks, as well as downloading them & finding new ones.
//!
//! There are several structs which represent the different stages
//! that go on in downloading and playing tracks. The proccess for fetching tracks,
//! and what structs are relevant in each step, are as follows.
//! that go on in downloading and playing tracks. When first queued,
//! the downloader will return a [`Queued`] track.
//!
//! First Stage, when a track is initially fetched.
//! 1. Raw entry selected from track list.
//! 2. Raw entry split into path & display name.
//! 3. Track data fetched, and [`QueuedTrack`] is created which includes a [`TrackName`] that may be raw.
//!
//! Second Stage, when a track is played.
//! 1. Track data is decoded.
//! 2. [`Info`] created from decoded data.
//! 3. [`Decoded`] made from [`Info`] and the original decoded data.
//! Then, when it's time to play the track, it is decoded into
//! a [`Decoded`] track, which includes all the information
//! in the form of [`Info`].
use std::{fmt::Debug, io::Cursor, time::Duration};
use bytes::Bytes;
use rodio::{Decoder, Source as _};
use unicode_segmentation::UnicodeSegmentation;
use unicode_segmentation::UnicodeSegmentation as _;
pub mod list;
pub use list::List;
@ -27,7 +21,7 @@ pub mod error;
pub mod format;
pub use error::{Error, Result};
use crate::tracks::error::WithTrackContext;
use crate::tracks::error::WithTrackContext as _;
/// Just a shorthand for a decoded [Bytes].
pub type DecodedData = Decoder<Cursor<Bytes>>;
@ -35,7 +29,7 @@ pub type DecodedData = Decoder<Cursor<Bytes>>;
/// Tracks which are still waiting in the queue, and can't be played yet.
///
/// This means that only the data & track name are included.
#[derive(PartialEq)]
#[derive(PartialEq, Eq)]
pub struct Queued {
/// Display name of the track.
pub display: String,
@ -66,6 +60,7 @@ impl Queued {
Decoded::new(self)
}
/// Creates a new queued track.
pub fn new(path: String, data: Bytes, display: Option<String>) -> Result<Self> {
let display = match display {
None => self::format::name(&path)?,
@ -73,8 +68,8 @@ impl Queued {
};
Ok(Self {
path,
display,
path,
data,
})
}
@ -122,7 +117,7 @@ impl Info {
}
}
/// This struct is seperate from [Track] since it is generated lazily from
/// This struct is separate from [Track] since it is generated lazily from
/// a track, and not when the track is first downloaded.
pub struct Decoded {
/// Has both the formatted name and some information from the decoded data.
@ -138,7 +133,7 @@ impl Decoded {
pub fn new(track: Queued) -> Result<Self> {
let (path, display) = (track.path.clone(), track.display.clone());
let data = Decoder::builder()
.with_byte_len(track.data.len().try_into().unwrap())
.with_byte_len(track.data.len().try_into()?)
.with_data(Cursor::new(track.data))
.build()
.track(track.display)?;

View File

@ -19,6 +19,9 @@ pub enum Kind {
#[error("unable to fetch data: {0}")]
Request(#[from] reqwest::Error),
#[error("couldn't handle integer track length: {0}")]
Integer(#[from] std::num::TryFromIntError),
}
#[derive(Debug, thiserror::Error)]

View File

@ -1,10 +1,10 @@
use convert_case::{Case, Casing};
use convert_case::{Case, Casing as _};
use lazy_static::lazy_static;
use regex::Regex;
use std::path::Path;
use url::form_urlencoded;
use super::error::WithTrackContext;
use super::error::WithTrackContext as _;
lazy_static! {
static ref MASTER_PATTERNS: [Regex; 5] = [
@ -84,7 +84,7 @@ pub fn name(name: &str) -> super::Result<String> {
// If the entire name of the track is a number, then just return it.
if skip == name.len() {
Ok(name.trim().to_string())
Ok(name.trim().to_owned())
} else {
// We've already checked before that the bound is at an ASCII digit.
#[allow(clippy::string_slice)]

View File

@ -6,8 +6,8 @@ use std::{
sync::atomic::{AtomicU8, Ordering},
};
use bytes::{BufMut, Bytes, BytesMut};
use futures::StreamExt;
use bytes::{BufMut as _, Bytes, BytesMut};
use futures::StreamExt as _;
use reqwest::Client;
use tokio::fs;
@ -15,7 +15,7 @@ use crate::{
data_dir,
tracks::{
self,
error::{self, WithTrackContext},
error::{self, WithTrackContext as _},
},
};
@ -114,7 +114,7 @@ impl List {
while let Some(item) = stream.next().await {
let chunk = item.track(track)?;
downloaded = min(downloaded + (chunk.len() as u64), total);
let rounded = ((downloaded as f32) / (total as f32) * 100.0).round() as u8;
let rounded = ((downloaded as f64) / (total as f64) * 100.0).round() as u8;
progress.store(rounded, Ordering::Relaxed);
bytes.put(chunk);

View File

@ -22,8 +22,8 @@ pub mod mpris;
type Result<T> = std::result::Result<T, Error>;
/// The error type for the UI, which is used to handle errors that occur
/// while drawing the UI or handling input.
/// The error type for the UI, which is used to handle errors
/// that occur while drawing the UI or handling input.
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("unable to convert number")]
@ -36,7 +36,7 @@ pub enum Error {
CrateSend(#[from] tokio::sync::mpsc::error::SendError<crate::Message>),
#[error("sharing state between backend and frontend failed")]
UiSend(#[from] tokio::sync::broadcast::error::SendError<Update>),
Send(#[from] tokio::sync::broadcast::error::SendError<Update>),
#[cfg(feature = "mpris")]
#[error("mpris bus error")]
@ -47,6 +47,11 @@ pub enum Error {
Fdo(#[from] mpris_server::zbus::fdo::Error),
}
/// The UI state, which is all of the information that
/// the user interface needs to display to the user.
///
/// It should be noted that this is also used by MPRIS to keep
/// track of state.
#[derive(Clone)]
pub struct State {
pub sink: Arc<rodio::Sink>,
@ -60,19 +65,26 @@ pub struct State {
}
impl State {
pub fn initial(sink: Arc<rodio::Sink>, width: usize, current: Current, list: String) -> Self {
/// Creates an initial UI state.
pub fn initial(sink: Arc<rodio::Sink>, width: usize, list: String) -> Self {
let width = 21 + width.min(32) * 2;
Self {
width,
sink,
current,
list,
current: Current::default(),
bookmarked: false,
timer: None,
}
}
}
/// A UI update sent out by the main player thread, which may
/// not be immediately applied by the UI.
///
/// This corresponds to user actions, like bookmarking a track,
/// skipping, or changing the volume. The difference is that it also
/// contains the new information about the track.
#[derive(Debug, Clone)]
pub enum Update {
Track(Current),
@ -81,12 +93,16 @@ pub enum Update {
Quit,
}
/// Just a simple wrapper for the two primary tasks that the UI
/// requires to function.
#[derive(Debug)]
struct Tasks {
render: JoinHandle<Result<()>>,
input: JoinHandle<Result<()>>,
}
/// The UI handle for controlling the state of the UI, as well as
/// updating MPRIS information and other small interfacing tasks.
pub struct Handle {
tasks: Tasks,
pub environment: Environment,
@ -102,6 +118,14 @@ impl Drop for Handle {
}
impl Handle {
/// The main UI process, which will both render the UI to the terminal
/// and also update state.
///
/// It does both of these things at a fixed interval, due to things
/// like the track duration changing too frequently.
///
/// `rx` is the receiver for state updates, `state` the initial state,
/// and `params` specifies aesthetic options that are specified by the user.
async fn ui(
mut rx: broadcast::Receiver<Update>,
mut state: State,
@ -111,8 +135,6 @@ impl Handle {
let mut window = Window::new(state.width, params.borderless);
loop {
interface::draw(&mut state, &mut window, params)?;
if let Ok(message) = rx.try_recv() {
match message {
Update::Track(track) => state.current = track,
@ -122,12 +144,15 @@ impl Handle {
}
};
interface::draw(&mut state, &mut window, params)?;
interval.tick().await;
}
Ok(())
}
/// Initializes the UI itself, along with all of the tasks that are related to it.
#[allow(clippy::unused_async)]
pub async fn init(
tx: Sender<crate::Message>,
updater: broadcast::Receiver<ui::Update>,

View File

@ -45,7 +45,7 @@ impl Environment {
panic::set_hook(Box::new(move |info| {
let _ = environment.cleanup(false);
eprintln!("panic: {}", info);
eprintln!("panic: {info}");
}));
Ok(environment)

View File

@ -26,7 +26,7 @@ impl From<&Args> for Params {
}
pub(crate) fn menu(state: &mut ui::State, params: Params) -> Vec<String> {
let action = components::action(&state, state.width);
let action = components::action(state, state.width);
let middle = match state.timer {
Some(timer) => {
@ -38,7 +38,7 @@ pub(crate) fn menu(state: &mut ui::State, params: Params) -> Vec<String> {
components::audio_bar(state.width - 17, volume, &percentage)
}
None => components::progress_bar(&state, state.width - 16),
None => components::progress_bar(state, state.width - 16),
};
let controls = components::controls(state.width);

View File

@ -263,8 +263,8 @@ pub struct Server {
/// The inner MPRIS server.
inner: mpris_server::Server<Player>,
/// Broadcast reciever.
reciever: broadcast::Receiver<Update>,
/// Broadcast receiver.
receiver: broadcast::Receiver<Update>,
}
impl Server {
@ -273,16 +273,17 @@ impl Server {
&mut self,
properties: impl IntoIterator<Item = mpris_server::Property> + Send + Sync,
) -> ui::Result<()> {
while let Ok(update) = self.reciever.try_recv() {
while let Ok(update) = self.receiver.try_recv() {
if let Update::Track(current) = update {
self.player().current.swap(Arc::new(current));
}
}
self.inner.properties_changed(properties).await?;
self.inner.properties_changed(properties).await?;
Ok(())
}
/// Updates the volume with the latest information.
pub async fn update_volume(&mut self) -> ui::Result<()> {
self.changed(vec![Property::Volume(self.player().sink.volume().into())])
.await?;
@ -290,7 +291,7 @@ impl Server {
Ok(())
}
/// Shorthand to emit a `PropertiesChanged` signal, specifically about playback.
/// Updates the playback with the latest information.
pub async fn update_playback(&mut self) -> ui::Result<()> {
let status = self.player().playback_status().await?;
self.changed(vec![Property::PlaybackStatus(status)]).await?;
@ -298,6 +299,7 @@ impl Server {
Ok(())
}
/// Updates the current track data with the current information.
pub async fn update_metadata(&mut self) -> ui::Result<()> {
let metadata = self.player().metadata().await?;
self.changed(vec![Property::Metadata(metadata)]).await?;
@ -314,7 +316,7 @@ impl Server {
pub async fn new(
state: ui::State,
sender: mpsc::Sender<Message>,
reciever: broadcast::Receiver<Update>,
receiver: broadcast::Receiver<Update>,
) -> ui::Result<Server> {
let suffix = if env::var("LOWFI_FIXED_MPRIS_NAME").is_ok_and(|x| x == "1") {
String::from("lowfi")
@ -335,7 +337,7 @@ impl Server {
Ok(Self {
inner: server,
reciever,
receiver,
})
}
}

View File

@ -2,11 +2,11 @@ use std::io::{stdout, Stdout};
use crossterm::{
cursor::{MoveToColumn, MoveUp},
style::{Print, Stylize},
style::{Print, Stylize as _},
terminal::{Clear, ClearType},
};
use std::fmt::Write;
use unicode_segmentation::UnicodeSegmentation;
use std::fmt::Write as _;
use unicode_segmentation::UnicodeSegmentation as _;
/// Represents an abstraction for drawing the actual lowfi window itself.
///
@ -52,7 +52,7 @@ impl Window {
}
pub(crate) fn render(
&mut self,
&self,
content: Vec<String>,
space: bool,
testing: bool,

View File

@ -1,8 +1,11 @@
//! Persistent volume management.
use std::{num::ParseIntError, path::PathBuf};
use tokio::fs;
/// Shorthand for a [`Result`] with a persistent volume error.
type Result<T> = std::result::Result<T, Error>;
/// Errors which occur when loading/unloading persistent volume.
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("couldn't find config directory")]
@ -27,7 +30,7 @@ impl PersistentVolume {
/// Retrieves the config directory.
async fn config() -> Result<PathBuf> {
let config = dirs::config_dir()
.ok_or_else(|| Error::Directory)?
.ok_or(Error::Directory)?
.join(PathBuf::from("lowfi"));
if !config.exists() {