better error handling

This commit is contained in:
Joe Ardent 2025-07-28 13:46:50 -07:00
parent 6558e18dec
commit 9fff83a721
3 changed files with 68 additions and 49 deletions

View file

@ -128,10 +128,9 @@ impl App {
.state .state
.get() .get()
.unwrap() .unwrap()
.upload_requests .get_upload_request(id)
.lock()
.await .await
.get(&id).ok_or(LocalSendError::SessionInactive)?.clone(); .ok_or(LocalSendError::SessionInactive)?;
// TODO: replace this with ratatui widget dialog // TODO: replace this with ratatui widget dialog
let upload_confirmed = MessageDialogBuilder::default() let upload_confirmed = MessageDialogBuilder::default()

View file

@ -59,7 +59,7 @@ pub struct JoecalState {
pub running_state: Arc<Mutex<RunningState>>, pub running_state: Arc<Mutex<RunningState>>,
pub socket: Arc<UdpSocket>, pub socket: Arc<UdpSocket>,
pub client: reqwest::Client, pub client: reqwest::Client,
pub upload_requests: Arc<Mutex<HashMap<Julid, UnboundedSender<UploadDialog>>>>, upload_requests: Arc<Mutex<HashMap<Julid, UnboundedSender<UploadDialog>>>>,
shutdown_sender: OnceLock<ShutdownSender>, shutdown_sender: OnceLock<ShutdownSender>,
// the receiving end will be held by the application so it can update the UI based on backend // the receiving end will be held by the application so it can update the UI based on backend
// events // events
@ -130,6 +130,9 @@ impl JoecalState {
Listeners::Udp Listeners::Udp
} }
}); });
// TODO: add a task that periodically clears out the upload requests if
// they're too old; the keys are julids so they have the time in them
} }
pub async fn stop(&self) { pub async fn stop(&self) {
@ -147,6 +150,24 @@ impl JoecalState {
let mut peers = self.peers.lock().await; let mut peers = self.peers.lock().await;
peers.clear(); peers.clear();
} }
pub async fn get_upload_request(&self, id: Julid) -> Option<UnboundedSender<UploadDialog>> {
self.upload_requests.lock().await.get(&id).cloned()
}
pub async fn clear_upload_request(&self, id: Julid) {
let _ = self.upload_requests.lock().await.remove(&id);
}
/// Add a transmitter for an upload request confirmation dialog that the
/// application frontend can use to tell the Axum handler whether or not to
/// accept the upload.
///
/// IMPORTANT! Be sure to call `clear_upload_request(id)` when you're done
/// getting an answer back/before you exit!
pub async fn add_upload_request(&self, id: Julid, tx: UnboundedSender<UploadDialog>) {
self.upload_requests.lock().await.entry(id).insert_entry(tx);
}
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]

View file

@ -197,12 +197,8 @@ pub async fn register_prepare_upload(
let id = Julid::new(); let id = Julid::new();
let (tx, mut rx) = unbounded_channel(); let (tx, mut rx) = unbounded_channel();
state // be sure to clear this request before this function exits!
.upload_requests state.add_upload_request(id, tx).await;
.lock()
.await
.entry(id)
.insert_entry(tx);
let dialog_send = state.transfer_event_tx.send(TransferEvent::UploadRequest { let dialog_send = state.transfer_event_tx.send(TransferEvent::UploadRequest {
alias: req.info.alias.clone(), alias: req.info.alias.clone(),
@ -211,52 +207,55 @@ pub async fn register_prepare_upload(
match dialog_send { match dialog_send {
Ok(_) => {} Ok(_) => {}
Err(_e) => { Err(_e) => {
let _ = state.upload_requests.lock().await.remove(&id); state.clear_upload_request(id).await;
return StatusCode::INTERNAL_SERVER_ERROR.into_response(); return StatusCode::INTERNAL_SERVER_ERROR.into_response();
} }
} }
// safe to unwrap because it's only `None` when there are no more transmitters, let confirmation = rx.recv().await;
// and we still have the `tx` we created earlier state.clear_upload_request(id).await;
let result = rx.recv().await.unwrap();
let _ = state.upload_requests.lock().await.remove(&id);
if result == UploadDialog::UploadConfirm { let Some(confirmation) = confirmation else {
let session_id = id.as_string(); // the frontend must have dropped the tx before trying to send a reply back
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
};
let file_tokens: HashMap<String, String> = req if confirmation != UploadDialog::UploadConfirm {
.files return StatusCode::FORBIDDEN.into_response();
.keys()
.map(|id| (id.clone(), Julid::new().to_string())) // Replace with actual token logic
.collect();
let session = Session {
session_id: session_id.clone(),
files: req.files.clone(),
file_tokens: file_tokens.clone(),
receiver: state.device.clone(),
sender: req.info.clone(),
status: SessionStatus::Active,
addr,
};
state
.sessions
.lock()
.await
.insert(session_id.clone(), session);
(
StatusCode::OK,
Json(PrepareUploadResponse {
session_id,
files: file_tokens,
}),
)
.into_response()
} else {
StatusCode::FORBIDDEN.into_response()
} }
let session_id = id.as_string();
let file_tokens: HashMap<String, String> = req
.files
.keys()
.map(|id| (id.clone(), Julid::new().to_string())) // Replace with actual token logic
.collect();
let session = Session {
session_id: session_id.clone(),
files: req.files.clone(),
file_tokens: file_tokens.clone(),
receiver: state.device.clone(),
sender: req.info.clone(),
status: SessionStatus::Active,
addr,
};
state
.sessions
.lock()
.await
.insert(session_id.clone(), session);
(
StatusCode::OK,
Json(PrepareUploadResponse {
session_id,
files: file_tokens,
}),
)
.into_response()
} }
pub async fn register_upload( pub async fn register_upload(