mirror of
https://github.com/talwat/lowfi
synced 2025-06-07 20:12:44 +00:00
fix: begin work of improving error handling
This commit is contained in:
parent
e8b4b17f98
commit
1af976ad77
12
src/play.rs
12
src/play.rs
@ -1,5 +1,6 @@
|
|||||||
//! Responsible for the basic initialization & shutdown of the audio server & frontend.
|
//! Responsible for the basic initialization & shutdown of the audio server & frontend.
|
||||||
|
|
||||||
|
use std::env;
|
||||||
use std::io::{stdout, IsTerminal};
|
use std::io::{stdout, IsTerminal};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
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.
|
// Initialize the UI, as well as the internal communication channel.
|
||||||
let (tx, rx) = mpsc::channel(8);
|
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(
|
Some(task::spawn(ui::start(
|
||||||
Arc::clone(&player),
|
Arc::clone(&player),
|
||||||
tx.clone(),
|
tx.clone(),
|
||||||
@ -118,7 +119,14 @@ pub async fn play(args: Args) -> eyre::Result<()> {
|
|||||||
tx.send(Messages::Init).await?;
|
tx.send(Messages::Init).await?;
|
||||||
|
|
||||||
// Actually starts the player.
|
// 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.
|
// Save the volume.txt file for the next session.
|
||||||
PersistentVolume::save(player.sink.volume()).await?;
|
PersistentVolume::save(player.sink.volume()).await?;
|
||||||
|
@ -31,7 +31,7 @@ use mpris_server::{PlaybackStatus, PlayerInterface, Property};
|
|||||||
use crate::{
|
use crate::{
|
||||||
messages::Messages,
|
messages::Messages,
|
||||||
play::{PersistentVolume, SendableOutputStream},
|
play::{PersistentVolume, SendableOutputStream},
|
||||||
tracks::{self, bookmark, list::List},
|
tracks::{self, bookmark, list::List, TrackError},
|
||||||
Args,
|
Args,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -193,7 +193,7 @@ impl Player {
|
|||||||
/// This will play the next track, as well as refilling the buffer in the background.
|
/// 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.
|
/// 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.
|
// TODO: Consider replacing this with `unwrap_or_else` when async closures are stablized.
|
||||||
let track = self.tracks.write().await.pop_front();
|
let track = self.tracks.write().await.pop_front();
|
||||||
let track = if let Some(track) = track {
|
let track = if let Some(track) = track {
|
||||||
@ -209,7 +209,7 @@ impl Player {
|
|||||||
self.list.random(&self.client).await?
|
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.
|
// Set the current track.
|
||||||
self.set_current(decoded.info.clone());
|
self.set_current(decoded.info.clone());
|
||||||
@ -229,6 +229,7 @@ impl Player {
|
|||||||
player: Arc<Self>,
|
player: Arc<Self>,
|
||||||
itx: Sender<()>,
|
itx: Sender<()>,
|
||||||
tx: Sender<Messages>,
|
tx: Sender<Messages>,
|
||||||
|
debug: bool,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
// Stop the sink.
|
// Stop the sink.
|
||||||
player.sink.stop();
|
player.sink.stop();
|
||||||
@ -247,8 +248,12 @@ impl Player {
|
|||||||
// Notify the audio server that the next song has actually been downloaded.
|
// Notify the audio server that the next song has actually been downloaded.
|
||||||
tx.send(Messages::NewSong).await?;
|
tx.send(Messages::NewSong).await?;
|
||||||
}
|
}
|
||||||
Err(timeout) => {
|
Err(error) => {
|
||||||
if !timeout {
|
if !error.timeout {
|
||||||
|
if debug {
|
||||||
|
panic!("{:?}", error)
|
||||||
|
}
|
||||||
|
|
||||||
sleep(TIMEOUT).await;
|
sleep(TIMEOUT).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,6 +276,7 @@ impl Player {
|
|||||||
tx: Sender<Messages>,
|
tx: Sender<Messages>,
|
||||||
mut rx: Receiver<Messages>,
|
mut rx: Receiver<Messages>,
|
||||||
buf_size: usize,
|
buf_size: usize,
|
||||||
|
debug: bool,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
// Initialize the mpris player.
|
// Initialize the mpris player.
|
||||||
//
|
//
|
||||||
@ -287,7 +293,7 @@ impl Player {
|
|||||||
|
|
||||||
// `itx` is used to notify the `Downloader` when it needs to download new tracks.
|
// `itx` is used to notify the `Downloader` when it needs to download new tracks.
|
||||||
let downloader = Downloader::new(Arc::clone(&player), buf_size);
|
let downloader = Downloader::new(Arc::clone(&player), buf_size);
|
||||||
let (itx, downloader) = downloader.start();
|
let (itx, downloader) = downloader.start(debug);
|
||||||
|
|
||||||
// Start buffering tracks immediately.
|
// Start buffering tracks immediately.
|
||||||
Downloader::notify(&itx).await?;
|
Downloader::notify(&itx).await?;
|
||||||
@ -343,6 +349,7 @@ impl Player {
|
|||||||
Arc::clone(&player),
|
Arc::clone(&player),
|
||||||
itx.clone(),
|
itx.clone(),
|
||||||
tx.clone(),
|
tx.clone(),
|
||||||
|
debug,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Messages::Play => {
|
Messages::Play => {
|
||||||
|
@ -51,7 +51,7 @@ impl Downloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Actually starts & consumes the [Downloader].
|
/// Actually starts & consumes the [Downloader].
|
||||||
pub fn start(mut self) -> (Sender<()>, JoinHandle<()>) {
|
pub fn start(mut self, debug: bool) -> (Sender<()>, JoinHandle<()>) {
|
||||||
(
|
(
|
||||||
self.tx,
|
self.tx,
|
||||||
task::spawn(async move {
|
task::spawn(async move {
|
||||||
@ -62,8 +62,12 @@ impl Downloader {
|
|||||||
let data = self.player.list.random(&self.player.client).await;
|
let data = self.player.list.random(&self.player.client).await;
|
||||||
match data {
|
match data {
|
||||||
Ok(track) => self.player.tracks.write().await.push_back(track),
|
Ok(track) => self.player.tracks.write().await.push_back(track),
|
||||||
Err(timeout) => {
|
Err(error) => {
|
||||||
if !timeout {
|
if !error.timeout {
|
||||||
|
if debug {
|
||||||
|
panic!("{}", error)
|
||||||
|
}
|
||||||
|
|
||||||
sleep(TIMEOUT).await;
|
sleep(TIMEOUT).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
//! 2. [`Info`] created from decoded data.
|
//! 2. [`Info`] created from decoded data.
|
||||||
//! 3. [`Decoded`] made from [`Info`] and the original 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 bytes::Bytes;
|
||||||
use eyre::OptionExt as _;
|
use eyre::OptionExt as _;
|
||||||
@ -186,3 +186,48 @@ impl Decoded {
|
|||||||
Ok(Self { info, data })
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ use rand::Rng as _;
|
|||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
use crate::data_dir;
|
use crate::{data_dir, tracks::TrackError};
|
||||||
|
|
||||||
use super::Track;
|
use super::Track;
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ impl List {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Downloads a raw track, but doesn't decode it.
|
/// 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.
|
// If the track has a protocol, then we should ignore the base for it.
|
||||||
let full_path = if track.contains("://") {
|
let full_path = if track.contains("://") {
|
||||||
track.to_owned()
|
track.to_owned()
|
||||||
@ -62,23 +62,28 @@ impl List {
|
|||||||
|
|
||||||
let data: Bytes = if let Some(x) = full_path.strip_prefix("file://") {
|
let data: Bytes = if let Some(x) = full_path.strip_prefix("file://") {
|
||||||
let path = if x.starts_with("~") {
|
let path = if x.starts_with("~") {
|
||||||
let home_path = dirs::home_dir().ok_or(false)?;
|
let home_path = dirs::home_dir().ok_or(TrackError::empty(false))?;
|
||||||
let home = home_path.to_str().ok_or(false)?;
|
let home = home_path.to_str().ok_or(TrackError::empty(false))?;
|
||||||
|
|
||||||
x.replace("~", home)
|
x.replace("~", home)
|
||||||
} else {
|
} else {
|
||||||
x.to_owned()
|
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()
|
result.into()
|
||||||
} else {
|
} else {
|
||||||
let response = client
|
let response = client
|
||||||
.get(full_path.clone())
|
.get(full_path.clone())
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|x| x.is_timeout())?;
|
.map_err(|x| TrackError::new(x.is_timeout(), x))?;
|
||||||
response.bytes().await.map_err(|_| false)?
|
response
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|x| TrackError::new(false, x))?
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((data, full_path))
|
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,
|
/// 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.
|
/// 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 (path, custom_name) = self.random_path();
|
||||||
let (data, full_path) = self.download(&path, client).await?;
|
let (data, full_path) = self.download(&path, client).await?;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user