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()))?;
sender.send(UiEvent::Input(c.unwrap())).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 {
deferred_sender.send(DeferredEvent::Quit)?; //quit without saving
break 'core; //quit program without saving Key::Ctrl('q') | Key::Ctrl('c') => {
} deferred_sender.send(DeferredEvent::Quit)?;
Ok(UiEvent::Input(Key::Char('\t'))) => { break 'core; //quit program without saving
ui_data.state.next(); }
let state = ui_data.state.clone(); //cycle widgets
ui_data.info.set_info(&state); Key::Char('\t') => {
} ui_data.state.next();
Ok(UiEvent::Input(Key::Ctrl('r'))) => { let state = ui_data.state.clone();
ui_data.repo.confirm(); ui_data.info.set_info(&state);
deferred_sender }
.send(DeferredEvent::NewRepo(ui_data.repo.get())) //refresh repository
.unwrap(); Key::Ctrl('r') => {
} ui_data.repo.confirm();
Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::EditRepo => { deferred_sender.send(DeferredEvent::NewRepo(ui_data.repo.get()))?;
ui_data.repo.confirm(); }
deferred_sender //enter on selecting tags
.send(DeferredEvent::NewRepo(ui_data.repo.get())) Key::Char('\n') if ui_data.state == State::EditRepo => {
.unwrap(); ui_data.repo.confirm();
} deferred_sender.send(DeferredEvent::NewRepo(ui_data.repo.get()))?;
Ok(UiEvent::Input(Key::Backspace)) if ui_data.state == State::EditRepo => { }
ui_data.info.set_text("Editing Repository"); //delete last char on repository
ui_data.repo.handle_input(Key::Backspace); Key::Backspace if ui_data.state == State::EditRepo => {
} ui_data.info.set_text("Editing Repository");
Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) ui_data.repo.handle_input(Key::Backspace);
if ui_data.state == State::SelectTag => }
{ //moving up on selecting tags
deferred_sender.send(DeferredEvent::TagPrevious).unwrap(); Key::Up | Key::Char('k') if ui_data.state == State::SelectTag => {
ui_data.details = ui_data.tags.create_detail_widget(); deferred_sender.send(DeferredEvent::TagPrevious)?;
} 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 => //moving down on selecting tags
{ Key::Down | Key::Char('j') if ui_data.state == State::SelectTag => {
deferred_sender.send(DeferredEvent::TagNext).unwrap(); deferred_sender.send(DeferredEvent::TagNext)?;
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
ui_data.info.set_text("Editing Repository"); Key::Char(key) if ui_data.state == State::EditRepo => {
ui_data.repo.handle_input(Key::Char(key)); ui_data.info.set_text("Editing Repository");
} 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()))?;
sender.send(UiEvent::Input(c.unwrap())).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,111 +211,122 @@ 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
deferred_sender.send(DeferredEvent::Quit)?; Ok(UiEvent::Input(key)) => match key {
break 'core; //quit program without saving //quit without saving
} Key::Ctrl('q') | Key::Ctrl('c') => {
Ok(UiEvent::Input(Key::Char('\t'))) => { deferred_sender.send(DeferredEvent::Quit)?;
ui_data.state.next(); break 'core; //quit program without saving
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;
} }
Ok(_) => ui_data.info.set_text("Saved compose file"), //cycle widgets
}, Key::Char('\t') => {
Ok(UiEvent::Input(Key::Ctrl('r'))) => { ui_data.state.next();
ui_data.repo.confirm(); let state = ui_data.state.clone();
deferred_sender ui_data.info.set_info(&state);
.send(DeferredEvent::NewRepo(ui_data.repo.get())) }
.unwrap(); //save file
} Key::Ctrl('s') => match ui_data.services.save() {
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,
Err(e) => { Err(e) => {
ui_data.info.set_info(&format!("{}", e)); ui_data.info.set_info(&format!("{}", e));
continue; continue;
} }
Ok(tag) => tag, Ok(_) => ui_data.info.set_text("Saved compose file"),
}; },
repo.push(':'); //refresh repository
repo.push_str(&tag); Key::Ctrl('r') => {
ui_data.services.change_current_line(repo); ui_data.repo.confirm();
} deferred_sender
Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::EditRepo => { .send(DeferredEvent::NewRepo(ui_data.repo.get()))
ui_data.repo.confirm(); .unwrap();
deferred_sender }
.send(DeferredEvent::NewRepo(ui_data.repo.get())) //enter on selecting tags
.unwrap(); Key::Char('\n') if ui_data.state == State::SelectTag => {
} let mut repo = ui_data.repo.get();
Ok(UiEvent::Input(Key::Backspace)) if ui_data.state == State::EditRepo => { let tag = match ui_data.tags.get_selected() {
ui_data.info.set_text("Editing Repository"); Err(async_tag_list::Error::NextPageSelected) => continue,
ui_data.repo.handle_input(Key::Backspace); Err(e) => {
} ui_data.info.set_info(&format!("{}", e));
Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) continue;
if ui_data.state == State::SelectService }
&& ui_data.services.find_previous_match() => Ok(tag) => tag,
{ };
match ui_data.services.extract_repo() { repo.push(':');
Err(e) => ui_data.info.set_info(&format!("{}", e)), repo.push_str(&tag);
Ok(s) => { ui_data.services.change_current_line(repo);
let repo = match repository::check_repo(&s) { }
Err(e) => { //enter on editing repository
ui_data.info.set_info(&format!("{}", e)); Key::Char('\n') if ui_data.state == State::EditRepo => {
continue; ui_data.repo.confirm();
} deferred_sender
Ok(s) => s, .send(DeferredEvent::NewRepo(ui_data.repo.get()))
}; .unwrap();
ui_data.repo.set(repo.to_string()); }
deferred_sender //delete last char on repository
.send(DeferredEvent::NewRepo(ui_data.repo.get())) Key::Backspace if ui_data.state == State::EditRepo => {
.unwrap(); 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();
}
} }
} }
} //moving down on selecting service
Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) 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() =>
{ {
match ui_data.services.extract_repo() { match ui_data.services.extract_repo() {
Err(e) => ui_data.info.set_info(&format!("{}", e)), Err(e) => ui_data.info.set_info(&format!("{}", e)),
Ok(s) => { Ok(s) => {
let repo = match repository::check_repo(&s) { let repo = match repository::check_repo(&s) {
Err(e) => { Err(e) => {
ui_data.info.set_info(&format!("{}", e)); ui_data.info.set_info(&format!("{}", e));
continue; continue;
} }
Ok(s) => s, Ok(s) => s,
}; };
ui_data.repo.set(repo.to_string()); ui_data.repo.set(repo.to_string());
deferred_sender deferred_sender
.send(DeferredEvent::NewRepo(ui_data.repo.get())) .send(DeferredEvent::NewRepo(ui_data.repo.get()))
.unwrap(); .unwrap();
}
} }
} }
} //moving up on selecting tags
Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) Key::Up | Key::Char('k') if ui_data.state == State::SelectTag => {
if ui_data.state == State::SelectTag => deferred_sender.send(DeferredEvent::TagPrevious).unwrap();
{ }
deferred_sender.send(DeferredEvent::TagPrevious).unwrap(); //moving down on selecting tags
} Key::Down | Key::Char('j') if ui_data.state == State::SelectTag => {
Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) deferred_sender.send(DeferredEvent::TagNext).unwrap();
if ui_data.state == State::SelectTag => }
{ //append character on editing repository
deferred_sender.send(DeferredEvent::TagNext).unwrap(); Key::Char(key) if ui_data.state == State::EditRepo => {
} ui_data.info.set_text("Editing Repository");
Ok(UiEvent::Input(Key::Char(key))) if ui_data.state == State::EditRepo => { ui_data.repo.handle_input(Key::Char(key));
ui_data.info.set_text("Editing Repository"); }
ui_data.repo.handle_input(Key::Char(key)); //ignore all else input
} _ => {}
Ok(UiEvent::Input(_)) => {} },
Ok(UiEvent::RefreshOnNewData) => {} Ok(UiEvent::RefreshOnNewData) | Err(_) => {}
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 => (), for image in tags.get_tags().iter() {
Some(tags) => { self.lines.push(Line::Image(image.clone()));
for image in tags.get_tags().iter() {
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());
} }
} }