fix: begin work of improving error handling

This commit is contained in:
talwat 2025-06-04 22:16:52 +02:00
parent e8b4b17f98
commit 1af976ad77
5 changed files with 89 additions and 20 deletions

View File

@ -1,5 +1,6 @@
//! Responsible for the basic initialization & shutdown of the audio server & frontend.
use std::env;
use std::io::{stdout, IsTerminal};
use std::path::PathBuf;
use std::sync::Arc;
@ -104,7 +105,7 @@ pub async fn play(args: Args) -> eyre::Result<()> {
// Initialize the UI, as well as the internal communication channel.
let (tx, rx) = mpsc::channel(8);
let ui = if stdout().is_terminal() {
let ui = if stdout().is_terminal() && !(env::var("LOWFI_DISABLE_UI") == Ok("1".to_owned())) {
Some(task::spawn(ui::start(
Arc::clone(&player),
tx.clone(),
@ -118,7 +119,14 @@ pub async fn play(args: Args) -> eyre::Result<()> {
tx.send(Messages::Init).await?;
// Actually starts the player.
Player::play(Arc::clone(&player), tx.clone(), rx, args.buffer_size).await?;
Player::play(
Arc::clone(&player),
tx.clone(),
rx,
args.buffer_size,
args.debug,
)
.await?;
// Save the volume.txt file for the next session.
PersistentVolume::save(player.sink.volume()).await?;

View File

@ -31,7 +31,7 @@ use mpris_server::{PlaybackStatus, PlayerInterface, Property};
use crate::{
messages::Messages,
play::{PersistentVolume, SendableOutputStream},
tracks::{self, bookmark, list::List},
tracks::{self, bookmark, list::List, TrackError},
Args,
};
@ -193,7 +193,7 @@ impl Player {
/// This will play the next track, as well as refilling the buffer in the background.
///
/// This will also set `current` to the newly loaded song.
pub async fn next(&self) -> Result<tracks::Decoded, bool> {
pub async fn next(&self) -> Result<tracks::Decoded, tracks::TrackError> {
// TODO: Consider replacing this with `unwrap_or_else` when async closures are stablized.
let track = self.tracks.write().await.pop_front();
let track = if let Some(track) = track {
@ -209,7 +209,7 @@ impl Player {
self.list.random(&self.client).await?
};
let decoded = track.decode().map_err(|_| false)?;
let decoded = track.decode().map_err(|x| TrackError::new_eyre(false, x))?;
// Set the current track.
self.set_current(decoded.info.clone());
@ -229,6 +229,7 @@ impl Player {
player: Arc<Self>,
itx: Sender<()>,
tx: Sender<Messages>,
debug: bool,
) -> eyre::Result<()> {
// Stop the sink.
player.sink.stop();
@ -247,8 +248,12 @@ impl Player {
// Notify the audio server that the next song has actually been downloaded.
tx.send(Messages::NewSong).await?;
}
Err(timeout) => {
if !timeout {
Err(error) => {
if !error.timeout {
if debug {
panic!("{:?}", error)
}
sleep(TIMEOUT).await;
}
@ -271,6 +276,7 @@ impl Player {
tx: Sender<Messages>,
mut rx: Receiver<Messages>,
buf_size: usize,
debug: bool,
) -> eyre::Result<()> {
// Initialize the mpris player.
//
@ -287,7 +293,7 @@ impl Player {
// `itx` is used to notify the `Downloader` when it needs to download new tracks.
let downloader = Downloader::new(Arc::clone(&player), buf_size);
let (itx, downloader) = downloader.start();
let (itx, downloader) = downloader.start(debug);
// Start buffering tracks immediately.
Downloader::notify(&itx).await?;
@ -343,6 +349,7 @@ impl Player {
Arc::clone(&player),
itx.clone(),
tx.clone(),
debug,
));
}
Messages::Play => {

View File

@ -51,7 +51,7 @@ impl Downloader {
}
/// Actually starts & consumes the [Downloader].
pub fn start(mut self) -> (Sender<()>, JoinHandle<()>) {
pub fn start(mut self, debug: bool) -> (Sender<()>, JoinHandle<()>) {
(
self.tx,
task::spawn(async move {
@ -62,8 +62,12 @@ impl Downloader {
let data = self.player.list.random(&self.player.client).await;
match data {
Ok(track) => self.player.tracks.write().await.push_back(track),
Err(timeout) => {
if !timeout {
Err(error) => {
if !error.timeout {
if debug {
panic!("{}", error)
}
sleep(TIMEOUT).await;
}
}

View File

@ -15,7 +15,7 @@
//! 2. [`Info`] created from decoded data.
//! 3. [`Decoded`] made from [`Info`] and the original decoded data.
use std::{io::Cursor, time::Duration};
use std::{error::Error, io::Cursor, time::Duration};
use bytes::Bytes;
use eyre::OptionExt as _;
@ -186,3 +186,48 @@ impl Decoded {
Ok(Self { info, data })
}
}
#[derive(Debug)]
pub struct TrackError {
pub timeout: bool,
inner: Option<eyre::Error>,
}
impl<'a> std::fmt::Display for TrackError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"TrackError (timeout: {}): {:?}",
self.timeout, self.inner
)
}
}
impl<'a> std::error::Error for TrackError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.inner.as_ref().map(|e| e.as_ref())
}
}
impl TrackError {
pub fn new<T: Error + 'static + Send + Sync>(timeout: bool, inner: T) -> Self {
Self {
inner: Some(eyre::eyre!(inner)),
timeout,
}
}
pub fn new_eyre(timeout: bool, inner: eyre::Error) -> Self {
Self {
inner: Some(inner),
timeout,
}
}
pub fn empty(timeout: bool) -> Self {
Self {
inner: None,
timeout,
}
}
}

View File

@ -7,7 +7,7 @@ use rand::Rng as _;
use reqwest::Client;
use tokio::fs;
use crate::data_dir;
use crate::{data_dir, tracks::TrackError};
use super::Track;
@ -52,7 +52,7 @@ impl List {
}
/// Downloads a raw track, but doesn't decode it.
async fn download(&self, track: &str, client: &Client) -> Result<(Bytes, String), bool> {
async fn download(&self, track: &str, client: &Client) -> Result<(Bytes, String), TrackError> {
// If the track has a protocol, then we should ignore the base for it.
let full_path = if track.contains("://") {
track.to_owned()
@ -62,23 +62,28 @@ impl List {
let data: Bytes = if let Some(x) = full_path.strip_prefix("file://") {
let path = if x.starts_with("~") {
let home_path = dirs::home_dir().ok_or(false)?;
let home = home_path.to_str().ok_or(false)?;
let home_path = dirs::home_dir().ok_or(TrackError::empty(false))?;
let home = home_path.to_str().ok_or(TrackError::empty(false))?;
x.replace("~", home)
} else {
x.to_owned()
};
let result = tokio::fs::read(path).await.map_err(|_| false)?;
let result = tokio::fs::read(path)
.await
.map_err(|x| TrackError::new(false, x))?;
result.into()
} else {
let response = client
.get(full_path.clone())
.send()
.await
.map_err(|x| x.is_timeout())?;
response.bytes().await.map_err(|_| false)?
.map_err(|x| TrackError::new(x.is_timeout(), x))?;
response
.bytes()
.await
.map_err(|x| TrackError::new(false, x))?
};
Ok((data, full_path))
@ -88,7 +93,7 @@ impl List {
///
/// The Result's error is a bool, which is true if a timeout error occured,
/// and false otherwise. This tells lowfi if it shouldn't wait to try again.
pub async fn random(&self, client: &Client) -> Result<Track, bool> {
pub async fn random(&self, client: &Client) -> Result<Track, TrackError> {
let (path, custom_name) = self.random_path();
let (data, full_path) = self.download(&path, client).await?;