better error handling

This commit is contained in:
Thomas Eppers 2023-02-19 12:39:15 +01:00
parent 28ae313797
commit bd829f23ad
5 changed files with 222 additions and 194 deletions

View File

@ -25,4 +25,8 @@ pub enum Error {
/// error while handling requests /// error while handling requests
#[error("reqwest error: {0}")] #[error("reqwest error: {0}")]
Reqwest(#[from] reqwest::Error), Reqwest(#[from] reqwest::Error),
/// error while sending to channel
#[error("sending to channel error: {0}")]
ChannelSendError(#[from] std::sync::mpsc::SendError<crate::ui::UiEvent>),
} }

View File

@ -6,6 +6,11 @@ use anyhow::Result;
use crate::widget::service_switcher; use crate::widget::service_switcher;
use crate::Opt; use crate::Opt;
pub enum UiEvent {
Input(termion::event::Key),
RefreshOnNewData,
}
pub fn create_ui(opt: &Opt) -> Result<()> { pub fn create_ui(opt: &Opt) -> Result<()> {
let service_result = service_switcher::ServiceSwitcher::new(&opt.file); let service_result = service_switcher::ServiceSwitcher::new(&opt.file);
match service_result { match service_result {

View File

@ -8,11 +8,11 @@ use tui::Terminal;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::{Arc, Mutex}; 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::async_tag_list::{self, TagList};
use crate::widget::info; use crate::widget::{info, repo_entry};
use crate::widget::repo_entry;
use crate::Opt; use crate::Opt;
pub struct Ui { pub struct Ui {
@ -50,11 +50,6 @@ impl std::iter::Iterator for State {
} }
} }
pub enum UiEvent {
Input(Key),
RefreshOnNewData,
}
pub enum DeferredEvent { pub enum DeferredEvent {
Quit, Quit,
NewRepo(String), NewRepo(String),
@ -63,22 +58,21 @@ pub enum DeferredEvent {
} }
impl Ui { impl Ui {
/// create a thread for catching input and send them to core loop /// catch input and send them to core loop
pub fn spawn_input_channel(sender: mpsc::Sender<UiEvent>) { pub fn wait_for_input(sender: mpsc::Sender<UiEvent>) -> Result<(), Error> {
thread::spawn(move || loop { let stdin = std::io::stdin();
let stdin = io::stdin();
for c in stdin.keys() { for c in stdin.keys() {
sender.send(UiEvent::Input(c.unwrap())).unwrap(); sender.send(UiEvent::Input(c.unwrap()))?;
} }
}); Ok(())
} }
#[tokio::main] #[tokio::main]
pub async fn work_requests( pub async fn work_requests(
ui: Arc<Mutex<Ui>>, ui: &Arc<Mutex<Ui>>,
events: mpsc::Receiver<DeferredEvent>, events: mpsc::Receiver<DeferredEvent>,
sender: mpsc::Sender<UiEvent>, sender: mpsc::Sender<UiEvent>,
) { ) -> Result<(), Error> {
loop { loop {
match events.recv() { match events.recv() {
Ok(DeferredEvent::Quit) => break, Ok(DeferredEvent::Quit) => break,
@ -86,7 +80,7 @@ impl Ui {
{ {
let mut ui = ui.lock().unwrap(); let mut ui = ui.lock().unwrap();
ui.tags = TagList::with_status("fetching new tags..."); 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 list = async_tag_list::TagList::with_repo_name(name).await;
let mut ui = ui.lock().unwrap(); let mut ui = ui.lock().unwrap();
@ -102,7 +96,7 @@ impl Ui {
let mut ui = ui.lock().unwrap(); let mut ui = ui.lock().unwrap();
if ui.tags.at_end_of_list() { if ui.tags.at_end_of_list() {
ui.info.set_text("Fetching more tags..."); ui.info.set_text("Fetching more tags...");
sender.send(UiEvent::RefreshOnNewData).unwrap(); sender.send(UiEvent::RefreshOnNewData)?;
(true, ui.tags.clone()) (true, ui.tags.clone())
} else { } else {
(false, ui.tags.clone()) (false, ui.tags.clone())
@ -121,8 +115,9 @@ impl Ui {
ui.info.set_info(&e); ui.info.set_info(&e);
} }
}; };
sender.send(UiEvent::RefreshOnNewData).unwrap(); sender.send(UiEvent::RefreshOnNewData)?;
} }
Ok(())
} }
pub fn run(opt: &Opt) -> Result<()> { pub fn run(opt: &Opt) -> Result<()> {
@ -142,16 +137,25 @@ impl Ui {
let ui_clone = ui.clone(); let ui_clone = ui.clone();
let sender2 = sender.clone(); let sender2 = sender.clone();
std::thread::spawn(move || { 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 //setup tui
let stdout = io::stdout().into_raw_mode()?; let stdout = std::io::stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout); let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
//setup input thread //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 interaction loop
'core: loop { 'core: loop {
@ -190,48 +194,52 @@ impl Ui {
let event = receiver.recv(); let event = receiver.recv();
let mut ui_data = ui.lock().unwrap(); let mut ui_data = ui.lock().unwrap();
match event { match event {
Ok(UiEvent::Input(Key::Ctrl('q'))) | Ok(UiEvent::Input(Key::Ctrl('c'))) => { Ok(UiEvent::Input(key)) => match key {
//quit without saving
Key::Ctrl('q') | Key::Ctrl('c') => {
deferred_sender.send(DeferredEvent::Quit)?; deferred_sender.send(DeferredEvent::Quit)?;
break 'core; //quit program without saving break 'core; //quit program without saving
} }
Ok(UiEvent::Input(Key::Char('\t'))) => { //cycle widgets
Key::Char('\t') => {
ui_data.state.next(); ui_data.state.next();
let state = ui_data.state.clone(); let state = ui_data.state.clone();
ui_data.info.set_info(&state); ui_data.info.set_info(&state);
} }
Ok(UiEvent::Input(Key::Ctrl('r'))) => { //refresh repository
Key::Ctrl('r') => {
ui_data.repo.confirm(); ui_data.repo.confirm();
deferred_sender deferred_sender.send(DeferredEvent::NewRepo(ui_data.repo.get()))?;
.send(DeferredEvent::NewRepo(ui_data.repo.get()))
.unwrap();
} }
Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::EditRepo => { //enter on selecting tags
Key::Char('\n') if ui_data.state == State::EditRepo => {
ui_data.repo.confirm(); ui_data.repo.confirm();
deferred_sender deferred_sender.send(DeferredEvent::NewRepo(ui_data.repo.get()))?;
.send(DeferredEvent::NewRepo(ui_data.repo.get()))
.unwrap();
} }
Ok(UiEvent::Input(Key::Backspace)) if ui_data.state == State::EditRepo => { //delete last char on repository
Key::Backspace if ui_data.state == State::EditRepo => {
ui_data.info.set_text("Editing Repository"); ui_data.info.set_text("Editing Repository");
ui_data.repo.handle_input(Key::Backspace); ui_data.repo.handle_input(Key::Backspace);
} }
Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) //moving up on selecting tags
if ui_data.state == State::SelectTag => Key::Up | Key::Char('k') if ui_data.state == State::SelectTag => {
{ deferred_sender.send(DeferredEvent::TagPrevious)?;
deferred_sender.send(DeferredEvent::TagPrevious).unwrap();
ui_data.details = ui_data.tags.create_detail_widget(); ui_data.details = ui_data.tags.create_detail_widget();
} }
Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) //moving down on selecting tags
if ui_data.state == State::SelectTag => Key::Down | Key::Char('j') if ui_data.state == State::SelectTag => {
{ deferred_sender.send(DeferredEvent::TagNext)?;
deferred_sender.send(DeferredEvent::TagNext).unwrap();
ui_data.details = ui_data.tags.create_detail_widget(); ui_data.details = ui_data.tags.create_detail_widget();
} }
Ok(UiEvent::Input(Key::Char(key))) if ui_data.state == State::EditRepo => { //append character on editing repository
Key::Char(key) if ui_data.state == State::EditRepo => {
ui_data.info.set_text("Editing Repository"); ui_data.info.set_text("Editing Repository");
ui_data.repo.handle_input(Key::Char(key)); ui_data.repo.handle_input(Key::Char(key));
} }
//ignore all else input
_ => {} _ => {}
},
Ok(UiEvent::RefreshOnNewData) | Err(_) => {}
} }
} }

View File

@ -8,13 +8,12 @@ use tui::Terminal;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::{io, thread};
use super::UiEvent;
use crate::error::Error;
use crate::repository; use crate::repository;
use crate::widget::async_tag_list::{self, TagList}; use crate::widget::async_tag_list::{self, TagList};
use crate::widget::info; use crate::widget::{info, repo_entry, service_switcher};
use crate::widget::repo_entry;
use crate::widget::service_switcher;
use crate::Opt; use crate::Opt;
pub struct Ui { pub struct Ui {
@ -56,11 +55,6 @@ impl std::iter::Iterator for State {
} }
} }
pub enum UiEvent {
Input(Key),
RefreshOnNewData,
}
pub enum DeferredEvent { pub enum DeferredEvent {
Quit, Quit,
NewRepo(String), NewRepo(String),
@ -69,22 +63,21 @@ pub enum DeferredEvent {
} }
impl Ui { impl Ui {
/// create a thread for catching input and send them to core loop /// catch input and send them to core loop
pub fn spawn_input_channel(sender: mpsc::Sender<UiEvent>) { pub fn wait_for_input(sender: mpsc::Sender<UiEvent>) -> Result<(), Error> {
thread::spawn(move || loop { let stdin = std::io::stdin();
let stdin = io::stdin();
for c in stdin.keys() { for c in stdin.keys() {
sender.send(UiEvent::Input(c.unwrap())).unwrap(); sender.send(UiEvent::Input(c.unwrap()))?;
} }
}); Ok(())
} }
#[tokio::main] #[tokio::main]
pub async fn work_requests( pub async fn work_requests(
ui: Arc<Mutex<Ui>>, ui: &Arc<Mutex<Ui>>,
events: mpsc::Receiver<DeferredEvent>, events: mpsc::Receiver<DeferredEvent>,
sender: mpsc::Sender<UiEvent>, sender: mpsc::Sender<UiEvent>,
) { ) -> Result<(), Error> {
loop { loop {
match events.recv() { match events.recv() {
Ok(DeferredEvent::Quit) => break, Ok(DeferredEvent::Quit) => break,
@ -92,7 +85,7 @@ impl Ui {
{ {
let mut ui = ui.lock().unwrap(); let mut ui = ui.lock().unwrap();
ui.tags = TagList::with_status("Fetching new tags..."); 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 list = TagList::with_repo_name(name).await;
let mut ui = ui.lock().unwrap(); let mut ui = ui.lock().unwrap();
@ -108,7 +101,7 @@ impl Ui {
let mut ui = ui.lock().unwrap(); let mut ui = ui.lock().unwrap();
if ui.tags.at_end_of_list() { if ui.tags.at_end_of_list() {
ui.info.set_text("Fetching more tags..."); ui.info.set_text("Fetching more tags...");
sender.send(UiEvent::RefreshOnNewData).unwrap(); sender.send(UiEvent::RefreshOnNewData)?;
(true, ui.tags.clone()) (true, ui.tags.clone())
} else { } else {
(false, ui.tags.clone()) (false, ui.tags.clone())
@ -127,8 +120,9 @@ impl Ui {
ui.info.set_info(&e); 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<()> { pub fn run(opt: &Opt, switcher: service_switcher::ServiceSwitcher) -> Result<()> {
@ -149,16 +143,25 @@ impl Ui {
let ui_clone = ui.clone(); let ui_clone = ui.clone();
let sender2 = sender.clone(); let sender2 = sender.clone();
std::thread::spawn(move || { 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 //setup tui
let stdout = io::stdout().into_raw_mode()?; let stdout = std::io::stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout); let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
//setup input thread //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 interaction loop
'core: loop { 'core: loop {
@ -208,29 +211,36 @@ impl Ui {
let event = receiver.recv(); let event = receiver.recv();
let mut ui_data = ui.lock().unwrap(); let mut ui_data = ui.lock().unwrap();
match event { match event {
Ok(UiEvent::Input(Key::Ctrl('q'))) | Ok(UiEvent::Input(Key::Ctrl('c'))) => { //handling input
Ok(UiEvent::Input(key)) => match key {
//quit without saving
Key::Ctrl('q') | Key::Ctrl('c') => {
deferred_sender.send(DeferredEvent::Quit)?; deferred_sender.send(DeferredEvent::Quit)?;
break 'core; //quit program without saving break 'core; //quit program without saving
} }
Ok(UiEvent::Input(Key::Char('\t'))) => { //cycle widgets
Key::Char('\t') => {
ui_data.state.next(); ui_data.state.next();
let state = ui_data.state.clone(); let state = ui_data.state.clone();
ui_data.info.set_info(&state); ui_data.info.set_info(&state);
} }
Ok(UiEvent::Input(Key::Ctrl('s'))) => match ui_data.services.save() { //save file
Key::Ctrl('s') => match ui_data.services.save() {
Err(e) => { Err(e) => {
ui_data.info.set_info(&format!("{}", e)); ui_data.info.set_info(&format!("{}", e));
continue; continue;
} }
Ok(_) => ui_data.info.set_text("Saved compose file"), Ok(_) => ui_data.info.set_text("Saved compose file"),
}, },
Ok(UiEvent::Input(Key::Ctrl('r'))) => { //refresh repository
Key::Ctrl('r') => {
ui_data.repo.confirm(); ui_data.repo.confirm();
deferred_sender deferred_sender
.send(DeferredEvent::NewRepo(ui_data.repo.get())) .send(DeferredEvent::NewRepo(ui_data.repo.get()))
.unwrap(); .unwrap();
} }
Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::SelectTag => { //enter on selecting tags
Key::Char('\n') if ui_data.state == State::SelectTag => {
let mut repo = ui_data.repo.get(); let mut repo = ui_data.repo.get();
let tag = match ui_data.tags.get_selected() { let tag = match ui_data.tags.get_selected() {
Err(async_tag_list::Error::NextPageSelected) => continue, Err(async_tag_list::Error::NextPageSelected) => continue,
@ -244,17 +254,20 @@ impl Ui {
repo.push_str(&tag); repo.push_str(&tag);
ui_data.services.change_current_line(repo); ui_data.services.change_current_line(repo);
} }
Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::EditRepo => { //enter on editing repository
Key::Char('\n') if ui_data.state == State::EditRepo => {
ui_data.repo.confirm(); ui_data.repo.confirm();
deferred_sender deferred_sender
.send(DeferredEvent::NewRepo(ui_data.repo.get())) .send(DeferredEvent::NewRepo(ui_data.repo.get()))
.unwrap(); .unwrap();
} }
Ok(UiEvent::Input(Key::Backspace)) if ui_data.state == State::EditRepo => { //delete last char on repository
Key::Backspace if ui_data.state == State::EditRepo => {
ui_data.info.set_text("Editing Repository"); ui_data.info.set_text("Editing Repository");
ui_data.repo.handle_input(Key::Backspace); ui_data.repo.handle_input(Key::Backspace);
} }
Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) //moving up on selecting service
Key::Up | Key::Char('k')
if ui_data.state == State::SelectService if ui_data.state == State::SelectService
&& ui_data.services.find_previous_match() => && ui_data.services.find_previous_match() =>
{ {
@ -275,7 +288,8 @@ impl Ui {
} }
} }
} }
Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) //moving down on selecting service
Key::Down | Key::Char('j')
if ui_data.state == State::SelectService if ui_data.state == State::SelectService
&& ui_data.services.find_next_match() => && ui_data.services.find_next_match() =>
{ {
@ -296,23 +310,23 @@ impl Ui {
} }
} }
} }
Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) //moving up on selecting tags
if ui_data.state == State::SelectTag => Key::Up | Key::Char('k') if ui_data.state == State::SelectTag => {
{
deferred_sender.send(DeferredEvent::TagPrevious).unwrap(); deferred_sender.send(DeferredEvent::TagPrevious).unwrap();
} }
Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) //moving down on selecting tags
if ui_data.state == State::SelectTag => Key::Down | Key::Char('j') if ui_data.state == State::SelectTag => {
{
deferred_sender.send(DeferredEvent::TagNext).unwrap(); deferred_sender.send(DeferredEvent::TagNext).unwrap();
} }
Ok(UiEvent::Input(Key::Char(key))) if ui_data.state == State::EditRepo => { //append character on editing repository
Key::Char(key) if ui_data.state == State::EditRepo => {
ui_data.info.set_text("Editing Repository"); ui_data.info.set_text("Editing Repository");
ui_data.repo.handle_input(Key::Char(key)); ui_data.repo.handle_input(Key::Char(key));
} }
Ok(UiEvent::Input(_)) => {} //ignore all else input
Ok(UiEvent::RefreshOnNewData) => {} _ => {}
Err(_) => {} },
Ok(UiEvent::RefreshOnNewData) | Err(_) => {}
} }
} }

View File

@ -160,17 +160,14 @@ impl TagList {
let next_page = self.lines.pop(); let next_page = self.lines.pop();
//add tags //add tags
match &self.tags { if let Some(tags) = &self.tags {
None => (),
Some(tags) => {
for image in tags.get_tags().iter() { for image in tags.get_tags().iter() {
self.lines.push(Line::Image(image.clone())); self.lines.push(Line::Image(image.clone()));
} }
} }
}
//readd next page item //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()); self.lines.push(next_page.unwrap());
} }
} }