From bd829f23ad429cb9a4e65e0de26ac8c02dbe833b Mon Sep 17 00:00:00 2001 From: Thomas Eppers Date: Sun, 19 Feb 2023 12:39:15 +0100 Subject: [PATCH] better error handling --- src/error.rs | 4 + src/ui/mod.rs | 5 + src/ui/no_yaml_found.rs | 140 ++++++++++--------- src/ui/yaml_found.rs | 256 ++++++++++++++++++----------------- src/widget/async_tag_list.rs | 11 +- 5 files changed, 222 insertions(+), 194 deletions(-) diff --git a/src/error.rs b/src/error.rs index 7839a8c..6472888 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,4 +25,8 @@ pub enum Error { /// error while handling requests #[error("reqwest error: {0}")] Reqwest(#[from] reqwest::Error), + + /// error while sending to channel + #[error("sending to channel error: {0}")] + ChannelSendError(#[from] std::sync::mpsc::SendError), } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index eb3805b..bce486c 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -6,6 +6,11 @@ use anyhow::Result; use crate::widget::service_switcher; use crate::Opt; +pub enum UiEvent { + Input(termion::event::Key), + RefreshOnNewData, +} + pub fn create_ui(opt: &Opt) -> Result<()> { let service_result = service_switcher::ServiceSwitcher::new(&opt.file); match service_result { diff --git a/src/ui/no_yaml_found.rs b/src/ui/no_yaml_found.rs index b3210ed..7a300d3 100644 --- a/src/ui/no_yaml_found.rs +++ b/src/ui/no_yaml_found.rs @@ -8,11 +8,11 @@ use tui::Terminal; use std::sync::mpsc; use std::sync::{Arc, Mutex}; -use std::{io, thread}; +use super::UiEvent; +use crate::error::Error; use crate::widget::async_tag_list::{self, TagList}; -use crate::widget::info; -use crate::widget::repo_entry; +use crate::widget::{info, repo_entry}; use crate::Opt; pub struct Ui { @@ -50,11 +50,6 @@ impl std::iter::Iterator for State { } } -pub enum UiEvent { - Input(Key), - RefreshOnNewData, -} - pub enum DeferredEvent { Quit, NewRepo(String), @@ -63,22 +58,21 @@ pub enum DeferredEvent { } impl Ui { - /// create a thread for catching input and send them to core loop - pub fn spawn_input_channel(sender: mpsc::Sender) { - thread::spawn(move || loop { - let stdin = io::stdin(); - for c in stdin.keys() { - sender.send(UiEvent::Input(c.unwrap())).unwrap(); - } - }); + /// catch input and send them to core loop + pub fn wait_for_input(sender: mpsc::Sender) -> Result<(), Error> { + let stdin = std::io::stdin(); + for c in stdin.keys() { + sender.send(UiEvent::Input(c.unwrap()))?; + } + Ok(()) } #[tokio::main] pub async fn work_requests( - ui: Arc>, + ui: &Arc>, events: mpsc::Receiver, sender: mpsc::Sender, - ) { + ) -> Result<(), Error> { loop { match events.recv() { Ok(DeferredEvent::Quit) => break, @@ -86,7 +80,7 @@ impl Ui { { let mut ui = ui.lock().unwrap(); ui.tags = TagList::with_status("fetching new tags..."); - sender.send(UiEvent::RefreshOnNewData).unwrap(); + sender.send(UiEvent::RefreshOnNewData)?; } let list = async_tag_list::TagList::with_repo_name(name).await; let mut ui = ui.lock().unwrap(); @@ -102,7 +96,7 @@ impl Ui { let mut ui = ui.lock().unwrap(); if ui.tags.at_end_of_list() { ui.info.set_text("Fetching more tags..."); - sender.send(UiEvent::RefreshOnNewData).unwrap(); + sender.send(UiEvent::RefreshOnNewData)?; (true, ui.tags.clone()) } else { (false, ui.tags.clone()) @@ -121,8 +115,9 @@ impl Ui { ui.info.set_info(&e); } }; - sender.send(UiEvent::RefreshOnNewData).unwrap(); + sender.send(UiEvent::RefreshOnNewData)?; } + Ok(()) } pub fn run(opt: &Opt) -> Result<()> { @@ -142,16 +137,25 @@ impl Ui { let ui_clone = ui.clone(); let sender2 = sender.clone(); std::thread::spawn(move || { - Self::work_requests(ui_clone, deferred_receiver, sender2); + if let Err(e) = Self::work_requests(&ui_clone, deferred_receiver, sender2) { + let mut ui = ui_clone.lock().unwrap(); + ui.info.set_info(&e); + } }); //setup tui - let stdout = io::stdout().into_raw_mode()?; + let stdout = std::io::stdout().into_raw_mode()?; let backend = TermionBackend::new(stdout); let mut terminal = Terminal::new(backend)?; //setup input thread - Self::spawn_input_channel(sender); + let ui_clone = ui.clone(); + std::thread::spawn(move || { + if let Err(e) = Self::wait_for_input(sender) { + let mut ui = ui_clone.lock().unwrap(); + ui.info.set_info(&e); + } + }); //core interaction loop 'core: loop { @@ -190,48 +194,52 @@ impl Ui { let event = receiver.recv(); let mut ui_data = ui.lock().unwrap(); match event { - Ok(UiEvent::Input(Key::Ctrl('q'))) | Ok(UiEvent::Input(Key::Ctrl('c'))) => { - deferred_sender.send(DeferredEvent::Quit)?; - break 'core; //quit program without saving - } - Ok(UiEvent::Input(Key::Char('\t'))) => { - ui_data.state.next(); - let state = ui_data.state.clone(); - ui_data.info.set_info(&state); - } - Ok(UiEvent::Input(Key::Ctrl('r'))) => { - ui_data.repo.confirm(); - deferred_sender - .send(DeferredEvent::NewRepo(ui_data.repo.get())) - .unwrap(); - } - Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::EditRepo => { - ui_data.repo.confirm(); - deferred_sender - .send(DeferredEvent::NewRepo(ui_data.repo.get())) - .unwrap(); - } - Ok(UiEvent::Input(Key::Backspace)) if ui_data.state == State::EditRepo => { - ui_data.info.set_text("Editing Repository"); - ui_data.repo.handle_input(Key::Backspace); - } - Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) - if ui_data.state == State::SelectTag => - { - deferred_sender.send(DeferredEvent::TagPrevious).unwrap(); - ui_data.details = ui_data.tags.create_detail_widget(); - } - Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) - if ui_data.state == State::SelectTag => - { - deferred_sender.send(DeferredEvent::TagNext).unwrap(); - ui_data.details = ui_data.tags.create_detail_widget(); - } - Ok(UiEvent::Input(Key::Char(key))) if ui_data.state == State::EditRepo => { - ui_data.info.set_text("Editing Repository"); - ui_data.repo.handle_input(Key::Char(key)); - } - _ => {} + Ok(UiEvent::Input(key)) => match key { + //quit without saving + Key::Ctrl('q') | Key::Ctrl('c') => { + deferred_sender.send(DeferredEvent::Quit)?; + break 'core; //quit program without saving + } + //cycle widgets + Key::Char('\t') => { + ui_data.state.next(); + let state = ui_data.state.clone(); + ui_data.info.set_info(&state); + } + //refresh repository + Key::Ctrl('r') => { + ui_data.repo.confirm(); + deferred_sender.send(DeferredEvent::NewRepo(ui_data.repo.get()))?; + } + //enter on selecting tags + Key::Char('\n') if ui_data.state == State::EditRepo => { + ui_data.repo.confirm(); + deferred_sender.send(DeferredEvent::NewRepo(ui_data.repo.get()))?; + } + //delete last char on repository + Key::Backspace if ui_data.state == State::EditRepo => { + ui_data.info.set_text("Editing Repository"); + ui_data.repo.handle_input(Key::Backspace); + } + //moving up on selecting tags + Key::Up | Key::Char('k') if ui_data.state == State::SelectTag => { + deferred_sender.send(DeferredEvent::TagPrevious)?; + ui_data.details = ui_data.tags.create_detail_widget(); + } + //moving down on selecting tags + Key::Down | Key::Char('j') if ui_data.state == State::SelectTag => { + deferred_sender.send(DeferredEvent::TagNext)?; + ui_data.details = ui_data.tags.create_detail_widget(); + } + //append character on editing repository + Key::Char(key) if ui_data.state == State::EditRepo => { + ui_data.info.set_text("Editing Repository"); + ui_data.repo.handle_input(Key::Char(key)); + } + //ignore all else input + _ => {} + }, + Ok(UiEvent::RefreshOnNewData) | Err(_) => {} } } diff --git a/src/ui/yaml_found.rs b/src/ui/yaml_found.rs index fbd571b..c553ca6 100644 --- a/src/ui/yaml_found.rs +++ b/src/ui/yaml_found.rs @@ -8,13 +8,12 @@ use tui::Terminal; use std::sync::mpsc; use std::sync::{Arc, Mutex}; -use std::{io, thread}; +use super::UiEvent; +use crate::error::Error; use crate::repository; use crate::widget::async_tag_list::{self, TagList}; -use crate::widget::info; -use crate::widget::repo_entry; -use crate::widget::service_switcher; +use crate::widget::{info, repo_entry, service_switcher}; use crate::Opt; pub struct Ui { @@ -56,11 +55,6 @@ impl std::iter::Iterator for State { } } -pub enum UiEvent { - Input(Key), - RefreshOnNewData, -} - pub enum DeferredEvent { Quit, NewRepo(String), @@ -69,22 +63,21 @@ pub enum DeferredEvent { } impl Ui { - /// create a thread for catching input and send them to core loop - pub fn spawn_input_channel(sender: mpsc::Sender) { - thread::spawn(move || loop { - let stdin = io::stdin(); - for c in stdin.keys() { - sender.send(UiEvent::Input(c.unwrap())).unwrap(); - } - }); + /// catch input and send them to core loop + pub fn wait_for_input(sender: mpsc::Sender) -> Result<(), Error> { + let stdin = std::io::stdin(); + for c in stdin.keys() { + sender.send(UiEvent::Input(c.unwrap()))?; + } + Ok(()) } #[tokio::main] pub async fn work_requests( - ui: Arc>, + ui: &Arc>, events: mpsc::Receiver, sender: mpsc::Sender, - ) { + ) -> Result<(), Error> { loop { match events.recv() { Ok(DeferredEvent::Quit) => break, @@ -92,7 +85,7 @@ impl Ui { { let mut ui = ui.lock().unwrap(); ui.tags = TagList::with_status("Fetching new tags..."); - sender.send(UiEvent::RefreshOnNewData).unwrap(); + sender.send(UiEvent::RefreshOnNewData)?; } let list = TagList::with_repo_name(name).await; let mut ui = ui.lock().unwrap(); @@ -108,7 +101,7 @@ impl Ui { let mut ui = ui.lock().unwrap(); if ui.tags.at_end_of_list() { ui.info.set_text("Fetching more tags..."); - sender.send(UiEvent::RefreshOnNewData).unwrap(); + sender.send(UiEvent::RefreshOnNewData)?; (true, ui.tags.clone()) } else { (false, ui.tags.clone()) @@ -127,8 +120,9 @@ impl Ui { ui.info.set_info(&e); } }; - sender.send(UiEvent::RefreshOnNewData).unwrap(); + sender.send(UiEvent::RefreshOnNewData)?; } + Ok(()) } pub fn run(opt: &Opt, switcher: service_switcher::ServiceSwitcher) -> Result<()> { @@ -149,16 +143,25 @@ impl Ui { let ui_clone = ui.clone(); let sender2 = sender.clone(); std::thread::spawn(move || { - Self::work_requests(ui_clone, deferred_receiver, sender2); + if let Err(e) = Self::work_requests(&ui_clone, deferred_receiver, sender2) { + let mut ui = ui_clone.lock().unwrap(); + ui.info.set_info(&e); + } }); //setup tui - let stdout = io::stdout().into_raw_mode()?; + let stdout = std::io::stdout().into_raw_mode()?; let backend = TermionBackend::new(stdout); let mut terminal = Terminal::new(backend)?; //setup input thread - Self::spawn_input_channel(sender); + let ui_clone = ui.clone(); + std::thread::spawn(move || { + if let Err(e) = Self::wait_for_input(sender) { + let mut ui = ui_clone.lock().unwrap(); + ui.info.set_info(&e); + } + }); //core interaction loop 'core: loop { @@ -208,111 +211,122 @@ impl Ui { let event = receiver.recv(); let mut ui_data = ui.lock().unwrap(); match event { - Ok(UiEvent::Input(Key::Ctrl('q'))) | Ok(UiEvent::Input(Key::Ctrl('c'))) => { - deferred_sender.send(DeferredEvent::Quit)?; - break 'core; //quit program without saving - } - Ok(UiEvent::Input(Key::Char('\t'))) => { - ui_data.state.next(); - let state = ui_data.state.clone(); - ui_data.info.set_info(&state); - } - Ok(UiEvent::Input(Key::Ctrl('s'))) => match ui_data.services.save() { - Err(e) => { - ui_data.info.set_info(&format!("{}", e)); - continue; + //handling input + Ok(UiEvent::Input(key)) => match key { + //quit without saving + Key::Ctrl('q') | Key::Ctrl('c') => { + deferred_sender.send(DeferredEvent::Quit)?; + break 'core; //quit program without saving } - Ok(_) => ui_data.info.set_text("Saved compose file"), - }, - Ok(UiEvent::Input(Key::Ctrl('r'))) => { - ui_data.repo.confirm(); - deferred_sender - .send(DeferredEvent::NewRepo(ui_data.repo.get())) - .unwrap(); - } - Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::SelectTag => { - let mut repo = ui_data.repo.get(); - let tag = match ui_data.tags.get_selected() { - Err(async_tag_list::Error::NextPageSelected) => continue, + //cycle widgets + Key::Char('\t') => { + ui_data.state.next(); + let state = ui_data.state.clone(); + ui_data.info.set_info(&state); + } + //save file + Key::Ctrl('s') => match ui_data.services.save() { Err(e) => { ui_data.info.set_info(&format!("{}", e)); continue; } - Ok(tag) => tag, - }; - repo.push(':'); - repo.push_str(&tag); - ui_data.services.change_current_line(repo); - } - Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::EditRepo => { - ui_data.repo.confirm(); - deferred_sender - .send(DeferredEvent::NewRepo(ui_data.repo.get())) - .unwrap(); - } - Ok(UiEvent::Input(Key::Backspace)) if ui_data.state == State::EditRepo => { - ui_data.info.set_text("Editing Repository"); - ui_data.repo.handle_input(Key::Backspace); - } - Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) - if ui_data.state == State::SelectService - && ui_data.services.find_previous_match() => - { - match ui_data.services.extract_repo() { - Err(e) => ui_data.info.set_info(&format!("{}", e)), - Ok(s) => { - let repo = match repository::check_repo(&s) { - Err(e) => { - ui_data.info.set_info(&format!("{}", e)); - continue; - } - Ok(s) => s, - }; - ui_data.repo.set(repo.to_string()); - deferred_sender - .send(DeferredEvent::NewRepo(ui_data.repo.get())) - .unwrap(); + Ok(_) => ui_data.info.set_text("Saved compose file"), + }, + //refresh repository + Key::Ctrl('r') => { + ui_data.repo.confirm(); + deferred_sender + .send(DeferredEvent::NewRepo(ui_data.repo.get())) + .unwrap(); + } + //enter on selecting tags + Key::Char('\n') if ui_data.state == State::SelectTag => { + let mut repo = ui_data.repo.get(); + let tag = match ui_data.tags.get_selected() { + Err(async_tag_list::Error::NextPageSelected) => continue, + Err(e) => { + ui_data.info.set_info(&format!("{}", e)); + continue; + } + Ok(tag) => tag, + }; + repo.push(':'); + repo.push_str(&tag); + ui_data.services.change_current_line(repo); + } + //enter on editing repository + Key::Char('\n') if ui_data.state == State::EditRepo => { + ui_data.repo.confirm(); + deferred_sender + .send(DeferredEvent::NewRepo(ui_data.repo.get())) + .unwrap(); + } + //delete last char on repository + Key::Backspace if ui_data.state == State::EditRepo => { + ui_data.info.set_text("Editing Repository"); + ui_data.repo.handle_input(Key::Backspace); + } + //moving up on selecting service + Key::Up | Key::Char('k') + if ui_data.state == State::SelectService + && ui_data.services.find_previous_match() => + { + match ui_data.services.extract_repo() { + Err(e) => ui_data.info.set_info(&format!("{}", e)), + Ok(s) => { + let repo = match repository::check_repo(&s) { + Err(e) => { + ui_data.info.set_info(&format!("{}", e)); + continue; + } + Ok(s) => s, + }; + ui_data.repo.set(repo.to_string()); + deferred_sender + .send(DeferredEvent::NewRepo(ui_data.repo.get())) + .unwrap(); + } } } - } - Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) - if ui_data.state == State::SelectService - && ui_data.services.find_next_match() => - { - match ui_data.services.extract_repo() { - Err(e) => ui_data.info.set_info(&format!("{}", e)), - Ok(s) => { - let repo = match repository::check_repo(&s) { - Err(e) => { - ui_data.info.set_info(&format!("{}", e)); - continue; - } - Ok(s) => s, - }; - ui_data.repo.set(repo.to_string()); - deferred_sender - .send(DeferredEvent::NewRepo(ui_data.repo.get())) - .unwrap(); + //moving down on selecting service + Key::Down | Key::Char('j') + if ui_data.state == State::SelectService + && ui_data.services.find_next_match() => + { + match ui_data.services.extract_repo() { + Err(e) => ui_data.info.set_info(&format!("{}", e)), + Ok(s) => { + let repo = match repository::check_repo(&s) { + Err(e) => { + ui_data.info.set_info(&format!("{}", e)); + continue; + } + Ok(s) => s, + }; + ui_data.repo.set(repo.to_string()); + deferred_sender + .send(DeferredEvent::NewRepo(ui_data.repo.get())) + .unwrap(); + } } } - } - Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) - if ui_data.state == State::SelectTag => - { - deferred_sender.send(DeferredEvent::TagPrevious).unwrap(); - } - Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) - if ui_data.state == State::SelectTag => - { - deferred_sender.send(DeferredEvent::TagNext).unwrap(); - } - Ok(UiEvent::Input(Key::Char(key))) if ui_data.state == State::EditRepo => { - ui_data.info.set_text("Editing Repository"); - ui_data.repo.handle_input(Key::Char(key)); - } - Ok(UiEvent::Input(_)) => {} - Ok(UiEvent::RefreshOnNewData) => {} - Err(_) => {} + //moving up on selecting tags + Key::Up | Key::Char('k') if ui_data.state == State::SelectTag => { + deferred_sender.send(DeferredEvent::TagPrevious).unwrap(); + } + //moving down on selecting tags + Key::Down | Key::Char('j') if ui_data.state == State::SelectTag => { + deferred_sender.send(DeferredEvent::TagNext).unwrap(); + } + //append character on editing repository + Key::Char(key) if ui_data.state == State::EditRepo => { + ui_data.info.set_text("Editing Repository"); + ui_data.repo.handle_input(Key::Char(key)); + } + //ignore all else input + _ => {} + }, + Ok(UiEvent::RefreshOnNewData) | Err(_) => {} } } diff --git a/src/widget/async_tag_list.rs b/src/widget/async_tag_list.rs index d6eebf6..fe901f7 100644 --- a/src/widget/async_tag_list.rs +++ b/src/widget/async_tag_list.rs @@ -160,17 +160,14 @@ impl TagList { let next_page = self.lines.pop(); //add tags - match &self.tags { - None => (), - Some(tags) => { - for image in tags.get_tags().iter() { - self.lines.push(Line::Image(image.clone())); - } + if let Some(tags) = &self.tags { + for image in tags.get_tags().iter() { + self.lines.push(Line::Image(image.clone())); } } //readd next page item - if let Some(_) = self.tags.as_ref().unwrap().next_page().await { + if (self.tags.as_ref().unwrap().next_page().await).is_some() { self.lines.push(next_page.unwrap()); } }