src: drop a lot of unwraps()

This commit is contained in:
2026-06-22 10:23:24 +02:00
parent 70ec40a880
commit 7cbff539df
8 changed files with 142 additions and 87 deletions
-1
View File
@@ -12,7 +12,6 @@ pub struct ArtifactRef<'a> {
archive: &'a Archive
}
pub struct ArtifactRefMut<'a> {
id: Uuid,
archive: &'a mut Archive
+3 -3
View File
@@ -32,7 +32,7 @@ pub struct BandcampSource;
impl DataSource for BandcampSource {
type Args = BandcampQueryArgs;
type Error = ();
type Error = bandcamp::Error;
async fn synchronize(&self, _artifact: &mut Artifact) -> Result<Vec<Artifact>, Self::Error> {
todo!()
@@ -47,11 +47,11 @@ impl DataSource for BandcampSource {
match result {
SearchResultItem::Artist(data) => {
// TODO: The artist and album detailed fetchers should also be separate args
let result = bandcamp::fetch_artist(data.artist_id).await.unwrap().into();
let result = bandcamp::fetch_artist(data.artist_id).await?.into();
json_results.push(result);
},
SearchResultItem::Album(data) => {
let result = bandcamp::fetch_album(data.band_id, data.album_id).await.unwrap().into();
let result = bandcamp::fetch_album(data.band_id, data.album_id).await?.into();
json_results.push(result);
},
SearchResultItem::Track(data) => {
+28 -24
View File
@@ -56,10 +56,30 @@ impl Into<Artifact> for BeetsTrack {
}
}
#[derive(Debug)]
#[allow(unused)]
pub enum BeetsError {
Command(std::io::Error),
Json(serde_json::Error),
EmptyQuery
}
impl From<std::io::Error> for BeetsError {
fn from(value: std::io::Error) -> Self {
Self::Command(value)
}
}
impl From<serde_json::Error> for BeetsError {
fn from(value: serde_json::Error) -> Self {
Self::Json(value)
}
}
pub struct BeetsDB;
impl BeetsDB {
async fn query_multi(&self, args: &BeatsQueryMultiArgs) -> Result<Vec<Artifact>, ()> {
async fn query_multi(&self, args: &BeatsQueryMultiArgs) -> Result<Vec<Artifact>, BeetsError> {
let mut ret = vec![];
for arg in &args.args {
for artifact in self.query_single(arg).await.unwrap_or_default() {
@@ -69,7 +89,7 @@ impl BeetsDB {
Ok(ret)
}
async fn query_single(&self, args: &BeatsQueryArgs) -> Result<Vec<Artifact>, ()> {
async fn query_single(&self, args: &BeatsQueryArgs) -> Result<Vec<Artifact>, BeetsError> {
let mut beets_cmd = Command::new("beet");
beets_cmd.args(["export", "-f", "json", "-i", "title,label,year,genres,album,artist,mb_trackid"]);
let mut valid = false;
@@ -100,29 +120,20 @@ impl BeetsDB {
if !valid {
log::warn!("Tried to execute an empty beets query");
return Err(())
return Err(BeetsError::EmptyQuery)
}
log::debug!("Executing beets: {:?}", beets_cmd);
if let Ok(output) = beets_cmd.stdout(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap().wait_with_output().await {
match serde_json::from_str::<Vec<BeetsTrack>>(str::from_utf8(&output.stdout).unwrap()) {
Ok(track) => Ok(track.into_iter().map(|t| { t.into()}).collect()),
Err(err) => {
log::error!("Failed to decode beets json: {:?}", err);
Err(())
}
}
} else {
log::error!("Unable to execute query!");
Err(())
}
let output = beets_cmd.stdout(Stdio::piped()).stderr(Stdio::null()).spawn()?.wait_with_output().await?;
let track = serde_json::from_str::<Vec<BeetsTrack>>(str::from_utf8(&output.stdout).unwrap())?;
Ok(track.into_iter().map(|t| { t.into()}).collect())
}
}
impl DataSource for BeetsDB {
type Args = BeatsQueryMultiArgs;
type Error = ();
type Error = BeetsError;
async fn synchronize(&self, artifact: &mut Artifact) -> Result<Vec<Artifact>, Self::Error> {
match artifact.contents {
@@ -134,7 +145,7 @@ impl DataSource for BeetsDB {
..Default::default()
};
let results = self.query(&BeatsQueryMultiArgs { args: vec![args] }).await.unwrap();
let results = self.query(&BeatsQueryMultiArgs { args: vec![args] }).await?;
if let Some(first) = results.first() {
artifact.merge(first.clone());
@@ -150,13 +161,6 @@ impl DataSource for BeetsDB {
}
fn query(&self, args: &Self::Args) -> impl Future<Output = Result<Vec<Artifact>, Self::Error>> {
/*let mut ret = vec![];
for arg in args.0 {
for artifact in self.query_single(&arg).await.unwrap_or_default() {
ret.push(artifact);
}
}
futures::ready!(Ok(ret))*/
self.query_multi(args)
}
+98 -46
View File
@@ -5,6 +5,41 @@ use oximedia_metering::vu_meter::VuMeter;
use serde::{Deserialize, Serialize};
use tokio::sync::*;
use crate::events::AudioRecordRequest;
#[derive(Debug)]
#[allow(unused)]
pub enum AudioError {
Jack(jack::Error),
AudioBufferSend(mpsc::error::SendError<Vec<f32>>),
AudioBufferRecv(mpsc::error::TryRecvError),
AudioRequestSend(watch::error::SendError<AudioRecordRequest>)
}
impl From<jack::Error> for AudioError {
fn from(value: jack::Error) -> Self {
Self::Jack(value)
}
}
impl From<mpsc::error::SendError<Vec<f32>>> for AudioError {
fn from(value: mpsc::error::SendError<Vec<f32>>) -> Self {
Self::AudioBufferSend(value)
}
}
impl From<mpsc::error::TryRecvError> for AudioError {
fn from(value: mpsc::error::TryRecvError) -> Self {
Self::AudioBufferRecv(value)
}
}
impl From<watch::error::SendError<AudioRecordRequest>> for AudioError {
fn from(value: watch::error::SendError<AudioRecordRequest>) -> Self {
Self::AudioRequestSend(value)
}
}
#[derive(Debug)]
pub struct JackClientRef {
killswitch: Option<oneshot::Sender<()>>
@@ -12,7 +47,7 @@ pub struct JackClientRef {
impl Drop for JackClientRef {
fn drop(&mut self) {
self.killswitch.take().unwrap().send(()).unwrap();
self.killswitch.take().expect("Killswitch was already dropped!").send(()).expect("Cannot fire Jack killswitch");
}
}
@@ -23,9 +58,9 @@ pub struct AudioInputControl {
}
impl AudioInputControl {
pub async fn next(&mut self) -> f64 {
self.volume_src.changed().await.unwrap();
*self.volume_src.borrow_and_update()
pub async fn next(&mut self) -> Result<f64, watch::error::RecvError> {
self.volume_src.changed().await?;
Ok(*self.volume_src.borrow_and_update())
}
}
@@ -76,28 +111,28 @@ struct AudioSource {
}
impl AudioSource {
fn new(client: &jack::Client, name: &str) -> (Self, AudioInStream) {
fn new(client: &jack::Client, name: &str) -> Result<(Self, AudioInStream), AudioError> {
let (sample_sink, receiver) = mpsc::channel(32);
let port = client.register_port(name, AudioIn::default()).unwrap();
(AudioSource {
let port = client.register_port(name, AudioIn::default())?;
Ok((AudioSource {
port,
sample_sink,
meter: VuMeter::new(client.sample_rate().into(), 1, None)
}, AudioInStream {
sample_rate: client.sample_rate(),
src: receiver
})
}))
}
fn process(&mut self, scope: &ProcessScope) -> Option<f64> {
if self.port.connected_count().unwrap() > 0 {
fn process(&mut self, scope: &ProcessScope) -> Result<Option<f64>, AudioError> {
if self.port.connected_count()? > 0 {
let buf: Vec<_> = self.port.as_slice(scope).iter().copied().collect();
self.meter.process_interleaved(&buf);
self.sample_sink.blocking_send(buf).unwrap();
self.sample_sink.blocking_send(buf)?;
self.meter.channel_vu(0)
Ok(self.meter.channel_vu(0))
} else {
None
Ok(None)
}
}
}
@@ -110,25 +145,24 @@ struct AudioSink {
}
impl AudioSink {
fn new(client: &jack::Client, name: &str) -> (Self, AudioOutStream) {
fn new(client: &jack::Client, name: &str) -> Result<(Self, AudioOutStream), AudioError> {
let (sender, sample_src) = mpsc::channel(32);
let port = client.register_port(name, AudioOut::default()).unwrap();
(AudioSink {
let port = client.register_port(name, AudioOut::default())?;
Ok((AudioSink {
output_buf: Vec::with_capacity(1024),
port,
sample_src
}, AudioOutStream {
sample_rate: client.sample_rate(),
sink: sender,
})
}))
}
fn process(&mut self, scope: &ProcessScope) {
if let Ok(mut next_outbuf) = self.sample_src.try_recv() {
self.output_buf.append(&mut next_outbuf);
}
fn process(&mut self, scope: &ProcessScope) -> Result<(), AudioError> {
let mut next_outbuf = self.sample_src.try_recv()?;
self.output_buf.append(&mut next_outbuf);
if self.port.connected_count().unwrap() > 0 && !self.output_buf.is_empty() {
if self.port.connected_count()? > 0 && !self.output_buf.is_empty() {
let outbuf = self.port.as_mut_slice(scope);
let mut next_segment: Vec<f32> = self.output_buf.drain(0..(outbuf.len()).min(self.output_buf.len())).collect();
let underrun = outbuf.len() - next_segment.len();
@@ -140,6 +174,8 @@ impl AudioSink {
outbuf.copy_from_slice(&next_segment);
}
Ok(())
}
}
@@ -151,7 +187,13 @@ struct AudioConfig {
impl AudioConfig {
pub fn load() -> Self {
if let Ok(contents) = std::fs::read_to_string("audio.json") {
serde_json::from_str(contents.as_str()).unwrap()
match serde_json::from_str(contents.as_str()) {
Err(err) => {
log::error!("Failed to load audio.json: {:?}", err);
Default::default()
},
Ok(ret) => ret
}
} else {
Default::default()
}
@@ -186,10 +228,7 @@ impl NotificationHandler for Notify {
}).next();
if let Some((role, Ok(target_port))) = port_match {
if !self.config.connections.contains_key(role) {
self.config.connections.insert(*role, Default::default());
}
let cfg_slot = self.config.connections.get_mut(role).unwrap();
let cfg_slot = self.config.connections.entry(*role).or_insert_with(|| Default::default());
if are_connected {
log::info!("{} connected to {}", role, target_port);
@@ -200,7 +239,9 @@ impl NotificationHandler for Notify {
}
let save_data = serde_json::to_string_pretty(&self.config).unwrap();
std::fs::write("audio.json", save_data).unwrap();
if let Err(err) = std::fs::write("audio.json", save_data) {
log::error!("Failed to write audio.json: {:?}", err);
}
}
}
}
@@ -213,11 +254,11 @@ pub async fn start_audio_input() -> (AudioInputControl, AudioInStream, AudioOutS
let (volume_sink, volume_src) = watch::channel(0.);
let (client, _status) = jack::Client::new("Eva-Cohost", ClientOptions::default() | ClientOptions::SESSION_ID).unwrap();
let (client, _status) = jack::Client::new("Eva-Cohost", ClientOptions::default() | ClientOptions::SESSION_ID).expect("Could not create JACK client!");
let (mut tts_sink, tts_stream) = AudioSink::new(&client, "tts-out");
let (mut sfx_sink, sfx_stream) = AudioSink::new(&client, "sfx-out");
let (mut mic_src, mic_stream) = AudioSource::new(&client, "microphone-in");
let (mut tts_sink, tts_stream) = AudioSink::new(&client, "tts-out").expect("Could not create TTS sink");
let (mut sfx_sink, sfx_stream) = AudioSink::new(&client, "sfx-out").expect("Could not create SFX sink");
let (mut mic_src, mic_stream) = AudioSource::new(&client, "microphone-in").expect("Could not create microphone source");
let notifier = Notify {
config,
@@ -248,30 +289,41 @@ pub async fn start_audio_input() -> (AudioInputControl, AudioInStream, AudioOutS
}
let handler = jack::contrib::ClosureProcessHandler::new(move |_client, scope| {
if let Some(next_vu) = mic_src.process(scope) {
volume_sink.send_if_modified(|v| {
let next_vu = (next_vu * 100.0).round() / 100.0;
if *v != next_vu {
*v = next_vu;
true
} else {
false
}
});
match mic_src.process(scope) {
Ok(Some(next_vu)) => {
volume_sink.send_if_modified(|v| {
let next_vu = (next_vu * 100.0).round() / 100.0;
if *v != next_vu {
*v = next_vu;
true
} else {
false
}
});
},
Ok(None) => (),
Err(err) => {
log::error!("Error while processing mic source: {:?}", err);
return jack::Control::Quit
}
}
for sink in [&mut tts_sink, &mut sfx_sink] {
sink.process(scope);
if let Err(err) = sink.process(scope) {
log::error!("Error while processing {:?} audio sink: {:?}", sink, err);
}
}
jack::Control::Continue
});
tokio::spawn(async move {
let async_client = client.activate_async(notifier, handler).unwrap();
let async_client = client.activate_async(notifier, handler).expect("Unable to start jack client!");
exit_rx.await.unwrap();
if let Err(err) = exit_rx.await {
log::warn!("Premature killswitch triggered: {:?}", err);
}
async_client.deactivate().unwrap();
async_client.deactivate().expect("Unable to stop the jack client");
});
(AudioInputControl {
+2 -2
View File
@@ -61,9 +61,9 @@ pub struct SaveData {
}
impl SaveData {
fn save(&self) {
fn save(&self) -> Result<(), std::io::Error> {
let save_data = serde_json::to_string_pretty(self).unwrap();
std::fs::write("save.json", save_data).unwrap();
std::fs::write("save.json", save_data)
}
}
+7 -7
View File
@@ -4,7 +4,7 @@ use async_openai::{Client, config::OpenAIConfig, types::{InputSource, audio::{Au
use tempfile::SpooledData;
use tokio::sync::{mpsc, watch};
use crate::{audio::AudioInStream, events::AudioRecordRequest};
use crate::{audio::{AudioError, AudioInStream}, events::AudioRecordRequest};
#[derive(Debug)]
pub struct TranscriptionControl {
@@ -13,16 +13,16 @@ pub struct TranscriptionControl {
}
impl TranscriptionControl {
pub fn start(&mut self) {
self.record_state_sink.send(AudioRecordRequest::Start).unwrap();
pub fn start(&mut self) -> Result<(), AudioError> {
Ok(self.record_state_sink.send(AudioRecordRequest::Start)?)
}
pub fn stop(&mut self) {
self.record_state_sink.send(AudioRecordRequest::Finish).unwrap();
pub fn stop(&mut self) -> Result<(), AudioError> {
Ok(self.record_state_sink.send(AudioRecordRequest::Finish)?)
}
pub async fn next(&mut self) -> String {
self.transcription_result_src.recv().await.unwrap()
pub async fn next(&mut self) -> Option<String> {
self.transcription_result_src.recv().await
}
}
+2 -2
View File
@@ -8,8 +8,8 @@ pub struct TtsControl {
}
impl TtsControl {
pub async fn speak(&self, text: String) {
self.request_sink.send(text).await.unwrap();
pub async fn speak(&self, text: String) -> Result<(), tokio::sync::mpsc::error::SendError<String>> {
self.request_sink.send(text).await
}
}
+2 -2
View File
@@ -299,10 +299,10 @@ impl Ui {
}
},
next_volume = self.audio.next() => {
self.audio_level = next_volume
self.audio_level = next_volume.expect("Audio volume event stream has died")
},
transcription_result = self.transcription.next() => {
self.predictions.insert(PredictionAction::ConversationAppend(ConversationEntry::Spoken(Speaker::User, transcription_result))).await;
self.predictions.insert(PredictionAction::ConversationAppend(ConversationEntry::Spoken(Speaker::User, transcription_result.expect("Transcription stream has died")))).await;
},
}
}