2023-10-06 22:35:27 -07:00
|
|
|
//! # iamb
|
|
|
|
//!
|
|
|
|
//! The iamb client loops over user input and commands, and turns them into actions, [some of
|
|
|
|
//! which][IambAction] are specific to iamb, and [some of which][Action] come from [modalkit]. When
|
|
|
|
//! adding new functionality, you will usually want to extend [IambAction] or one of its variants
|
|
|
|
//! (like [RoomAction][base::RoomAction]), and then add an appropriate [command][commands] or
|
|
|
|
//! [keybinding][keybindings].
|
|
|
|
//!
|
|
|
|
//! For more complicated changes, you may need to update [the async worker thread][worker], which
|
|
|
|
//! handles background Matrix tasks with [matrix-rust-sdk][matrix_sdk].
|
|
|
|
//!
|
|
|
|
//! Most rendering logic lives under the [windows] module, but [Matrix messages][message] have
|
|
|
|
//! their own module.
|
2022-12-29 18:00:59 -08:00
|
|
|
#![allow(clippy::manual_range_contains)]
|
|
|
|
#![allow(clippy::needless_return)]
|
|
|
|
#![allow(clippy::result_large_err)]
|
|
|
|
#![allow(clippy::bool_assert_comparison)]
|
|
|
|
use std::collections::VecDeque;
|
|
|
|
use std::convert::TryFrom;
|
|
|
|
use std::fmt::Display;
|
|
|
|
use std::fs::{create_dir_all, File};
|
2024-03-24 10:19:34 -07:00
|
|
|
use std::io::{stdout, BufWriter, Stdout, Write};
|
2022-12-29 18:00:59 -08:00
|
|
|
use std::ops::DerefMut;
|
|
|
|
use std::process;
|
2023-01-10 19:59:30 -08:00
|
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
2022-12-29 18:00:59 -08:00
|
|
|
use std::sync::Arc;
|
2024-03-22 00:46:46 +00:00
|
|
|
use std::time::{Duration, Instant};
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
use clap::Parser;
|
2024-03-02 15:00:29 -08:00
|
|
|
use matrix_sdk::crypto::encrypt_room_key_export;
|
2024-03-23 19:09:11 -07:00
|
|
|
use matrix_sdk::ruma::api::client::error::ErrorKind;
|
2024-02-28 09:03:28 -08:00
|
|
|
use matrix_sdk::ruma::OwnedUserId;
|
2024-03-09 22:49:40 -08:00
|
|
|
use modalkit::keybindings::InputBindings;
|
2024-03-02 15:00:29 -08:00
|
|
|
use rand::{distributions::Alphanumeric, Rng};
|
|
|
|
use temp_dir::TempDir;
|
2022-12-29 18:00:59 -08:00
|
|
|
use tokio::sync::Mutex as AsyncMutex;
|
|
|
|
use tracing_subscriber::FmtSubscriber;
|
|
|
|
|
|
|
|
use modalkit::crossterm::{
|
|
|
|
self,
|
|
|
|
cursor::Show as CursorShow,
|
2023-06-14 22:42:23 -07:00
|
|
|
event::{
|
|
|
|
poll,
|
|
|
|
read,
|
|
|
|
DisableBracketedPaste,
|
|
|
|
DisableFocusChange,
|
|
|
|
EnableBracketedPaste,
|
|
|
|
EnableFocusChange,
|
|
|
|
Event,
|
2024-03-20 22:13:47 -07:00
|
|
|
KeyEventKind,
|
2023-06-14 22:42:23 -07:00
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
execute,
|
|
|
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle},
|
|
|
|
};
|
|
|
|
|
2024-02-27 21:21:05 -08:00
|
|
|
use ratatui::{
|
2022-12-29 18:00:59 -08:00
|
|
|
backend::CrosstermBackend,
|
|
|
|
layout::Rect,
|
|
|
|
style::{Color, Style},
|
|
|
|
text::Span,
|
|
|
|
widgets::Paragraph,
|
|
|
|
Terminal,
|
|
|
|
};
|
|
|
|
|
|
|
|
mod base;
|
|
|
|
mod commands;
|
|
|
|
mod config;
|
|
|
|
mod keybindings;
|
|
|
|
mod message;
|
2024-03-22 00:46:46 +00:00
|
|
|
mod notifications;
|
2023-11-16 08:36:22 -08:00
|
|
|
mod preview;
|
2024-03-02 15:00:29 -08:00
|
|
|
mod sled_export;
|
2023-01-23 17:08:11 -08:00
|
|
|
mod util;
|
2022-12-29 18:00:59 -08:00
|
|
|
mod windows;
|
|
|
|
mod worker;
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
base::{
|
|
|
|
AsyncProgramStore,
|
|
|
|
ChatStore,
|
2023-03-04 12:23:17 -08:00
|
|
|
HomeserverAction,
|
2022-12-29 18:00:59 -08:00
|
|
|
IambAction,
|
|
|
|
IambError,
|
|
|
|
IambId,
|
|
|
|
IambInfo,
|
|
|
|
IambResult,
|
|
|
|
ProgramAction,
|
|
|
|
ProgramContext,
|
|
|
|
ProgramStore,
|
|
|
|
},
|
|
|
|
config::{ApplicationSettings, Iamb},
|
|
|
|
windows::IambWindow,
|
2023-03-04 12:23:17 -08:00
|
|
|
worker::{create_room, ClientWorker, LoginStyle, Requester},
|
2022-12-29 18:00:59 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
use modalkit::{
|
2024-02-28 23:00:25 -08:00
|
|
|
actions::{
|
|
|
|
Action,
|
|
|
|
Commandable,
|
|
|
|
Editable,
|
|
|
|
EditorAction,
|
|
|
|
InsertTextAction,
|
|
|
|
Jumpable,
|
|
|
|
Promptable,
|
|
|
|
Scrollable,
|
|
|
|
TabAction,
|
|
|
|
TabContainer,
|
|
|
|
TabCount,
|
|
|
|
WindowAction,
|
|
|
|
WindowContainer,
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
2024-02-28 23:00:25 -08:00
|
|
|
editing::{context::Resolve, key::KeyManager, store::Store},
|
|
|
|
errors::{EditError, UIError},
|
2024-02-27 21:21:05 -08:00
|
|
|
key::TerminalKey,
|
|
|
|
keybindings::{
|
|
|
|
dialog::{Pager, PromptYesNo},
|
|
|
|
BindingMachine,
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
2024-02-27 21:21:05 -08:00
|
|
|
prelude::*,
|
|
|
|
ui::FocusList,
|
|
|
|
};
|
|
|
|
|
|
|
|
use modalkit_ratatui::{
|
|
|
|
cmdbar::CommandBarState,
|
|
|
|
screen::{Screen, ScreenState, TabLayoutDescription},
|
|
|
|
windows::WindowLayoutDescription,
|
|
|
|
TerminalCursor,
|
|
|
|
TerminalExtOps,
|
|
|
|
Window,
|
2022-12-29 18:00:59 -08:00
|
|
|
};
|
|
|
|
|
2023-06-28 23:42:31 -07:00
|
|
|
fn config_tab_to_desc(
|
|
|
|
layout: config::WindowLayout,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> IambResult<WindowLayoutDescription<IambInfo>> {
|
|
|
|
let desc = match layout {
|
|
|
|
config::WindowLayout::Window { window } => {
|
|
|
|
let ChatStore { names, worker, .. } = &mut store.application;
|
|
|
|
|
|
|
|
let window = match window {
|
|
|
|
config::WindowPath::UserId(user_id) => {
|
|
|
|
let name = user_id.to_string();
|
|
|
|
let room_id = worker.join_room(name.clone())?;
|
|
|
|
names.insert(name, room_id.clone());
|
2024-03-09 00:47:05 -08:00
|
|
|
IambId::Room(room_id, None)
|
2023-06-28 23:42:31 -07:00
|
|
|
},
|
2024-03-09 00:47:05 -08:00
|
|
|
config::WindowPath::RoomId(room_id) => IambId::Room(room_id, None),
|
2023-06-28 23:42:31 -07:00
|
|
|
config::WindowPath::AliasId(alias) => {
|
|
|
|
let name = alias.to_string();
|
|
|
|
let room_id = worker.join_room(name.clone())?;
|
|
|
|
names.insert(name, room_id.clone());
|
2024-03-09 00:47:05 -08:00
|
|
|
IambId::Room(room_id, None)
|
2023-06-28 23:42:31 -07:00
|
|
|
},
|
|
|
|
config::WindowPath::Window(id) => id,
|
|
|
|
};
|
|
|
|
|
|
|
|
WindowLayoutDescription::Window { window, length: None }
|
|
|
|
},
|
|
|
|
config::WindowLayout::Split { split } => {
|
|
|
|
let children = split
|
|
|
|
.into_iter()
|
|
|
|
.map(|child| config_tab_to_desc(child, store))
|
|
|
|
.collect::<IambResult<Vec<_>>>()?;
|
|
|
|
|
|
|
|
WindowLayoutDescription::Split { children, length: None }
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(desc)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn setup_screen(
|
|
|
|
settings: ApplicationSettings,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> IambResult<ScreenState<IambWindow, IambInfo>> {
|
|
|
|
let cmd = CommandBarState::new(store);
|
|
|
|
let dims = crossterm::terminal::size()?;
|
|
|
|
let area = Rect::new(0, 0, dims.0, dims.1);
|
|
|
|
|
|
|
|
match settings.layout {
|
|
|
|
config::Layout::Restore => {
|
|
|
|
if let Ok(layout) = std::fs::read(&settings.layout_json) {
|
|
|
|
let tabs: TabLayoutDescription<IambInfo> =
|
|
|
|
serde_json::from_slice(&layout).map_err(IambError::from)?;
|
|
|
|
let tabs = tabs.to_layout(area.into(), store)?;
|
|
|
|
|
|
|
|
return Ok(ScreenState::from_list(tabs, cmd));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
config::Layout::New => {},
|
|
|
|
config::Layout::Config { tabs } => {
|
|
|
|
let mut list = FocusList::default();
|
|
|
|
|
|
|
|
for tab in tabs.into_iter() {
|
|
|
|
let tab = config_tab_to_desc(tab, store)?;
|
|
|
|
let tab = tab.to_layout(area.into(), store)?;
|
|
|
|
list.push(tab);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(ScreenState::from_list(list, cmd));
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
let win = settings
|
|
|
|
.tunables
|
|
|
|
.default_room
|
|
|
|
.and_then(|room| IambWindow::find(room, store).ok())
|
|
|
|
.or_else(|| IambWindow::open(IambId::Welcome, store).ok())
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
return Ok(ScreenState::new(win, cmd));
|
|
|
|
}
|
|
|
|
|
2023-10-06 22:35:27 -07:00
|
|
|
/// The main application state and event loop.
|
2022-12-29 18:00:59 -08:00
|
|
|
struct Application {
|
2023-06-28 23:42:31 -07:00
|
|
|
/// Terminal backend.
|
|
|
|
terminal: Terminal<CrosstermBackend<Stdout>>,
|
|
|
|
|
|
|
|
/// State for the Matrix client, editing, etc.
|
2022-12-29 18:00:59 -08:00
|
|
|
store: AsyncProgramStore,
|
2023-06-28 23:42:31 -07:00
|
|
|
|
|
|
|
/// UI state (open tabs, command bar, etc.) to use when rendering.
|
|
|
|
screen: ScreenState<IambWindow, IambInfo>,
|
|
|
|
|
|
|
|
/// Handle to communicate synchronously with the Matrix worker task.
|
2022-12-29 18:00:59 -08:00
|
|
|
worker: Requester,
|
2023-06-28 23:42:31 -07:00
|
|
|
|
|
|
|
/// Mapped keybindings.
|
2024-02-27 21:21:05 -08:00
|
|
|
bindings: KeyManager<TerminalKey, ProgramAction, RepeatType>,
|
2023-06-28 23:42:31 -07:00
|
|
|
|
|
|
|
/// Pending actions to run.
|
2022-12-29 18:00:59 -08:00
|
|
|
actstack: VecDeque<(ProgramAction, ProgramContext)>,
|
2023-06-28 23:42:31 -07:00
|
|
|
|
|
|
|
/// Whether or not the terminal is currently focused.
|
2023-06-14 22:42:23 -07:00
|
|
|
focused: bool,
|
2023-06-28 23:42:31 -07:00
|
|
|
|
|
|
|
/// The tab layout before the last executed [TabAction].
|
|
|
|
last_layout: Option<TabLayoutDescription<IambInfo>>,
|
2023-09-10 16:45:27 +03:00
|
|
|
|
|
|
|
/// Whether we need to do a full redraw (e.g., after running a subprocess).
|
|
|
|
dirty: bool,
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Application {
|
|
|
|
pub async fn new(
|
|
|
|
settings: ApplicationSettings,
|
|
|
|
store: AsyncProgramStore,
|
|
|
|
) -> IambResult<Application> {
|
|
|
|
let mut stdout = stdout();
|
|
|
|
crossterm::terminal::enable_raw_mode()?;
|
|
|
|
crossterm::execute!(stdout, EnterAlternateScreen)?;
|
2023-01-28 18:01:17 -08:00
|
|
|
crossterm::execute!(stdout, EnableBracketedPaste)?;
|
2023-06-14 22:42:23 -07:00
|
|
|
crossterm::execute!(stdout, EnableFocusChange)?;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
let title = format!("iamb ({})", settings.profile.user_id);
|
|
|
|
crossterm::execute!(stdout, SetTitle(title))?;
|
|
|
|
|
|
|
|
let backend = CrosstermBackend::new(stdout);
|
|
|
|
let terminal = Terminal::new(backend)?;
|
|
|
|
|
2024-03-09 22:49:40 -08:00
|
|
|
let mut bindings = crate::keybindings::setup_keybindings();
|
|
|
|
settings.setup(&mut bindings);
|
2022-12-29 18:00:59 -08:00
|
|
|
let bindings = KeyManager::new(bindings);
|
|
|
|
|
|
|
|
let mut locked = store.lock().await;
|
2023-06-28 23:42:31 -07:00
|
|
|
let screen = setup_screen(settings, locked.deref_mut())?;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
let worker = locked.application.worker.clone();
|
2023-11-16 08:36:22 -08:00
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
drop(locked);
|
|
|
|
|
|
|
|
let actstack = VecDeque::new();
|
|
|
|
|
|
|
|
Ok(Application {
|
|
|
|
store,
|
|
|
|
worker,
|
|
|
|
terminal,
|
|
|
|
bindings,
|
|
|
|
actstack,
|
|
|
|
screen,
|
2023-06-14 22:42:23 -07:00
|
|
|
focused: true,
|
2023-06-28 23:42:31 -07:00
|
|
|
last_layout: None,
|
2023-09-10 16:45:27 +03:00
|
|
|
dirty: true,
|
2022-12-29 18:00:59 -08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn redraw(&mut self, full: bool, store: &mut ProgramStore) -> Result<(), std::io::Error> {
|
2023-04-28 16:52:33 -07:00
|
|
|
let bindings = &mut self.bindings;
|
2023-06-14 22:42:23 -07:00
|
|
|
let focused = self.focused;
|
2022-12-29 18:00:59 -08:00
|
|
|
let sstate = &mut self.screen;
|
|
|
|
let term = &mut self.terminal;
|
|
|
|
|
2024-03-24 10:19:34 -07:00
|
|
|
if store.application.ring_bell {
|
|
|
|
store.application.ring_bell = term.backend_mut().write_all(&[7]).is_err();
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
if full {
|
|
|
|
term.clear()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
term.draw(|f| {
|
|
|
|
let area = f.size();
|
|
|
|
|
2023-04-28 16:52:33 -07:00
|
|
|
let modestr = bindings.show_mode();
|
|
|
|
let cursor = bindings.get_cursor_indicator();
|
|
|
|
let dialogstr = bindings.show_dialog(area.height as usize, area.width as usize);
|
|
|
|
|
|
|
|
// Don't show terminal cursor when we show a dialog.
|
|
|
|
let hide_cursor = !dialogstr.is_empty();
|
|
|
|
|
2024-03-22 00:46:46 +00:00
|
|
|
store.application.draw_curr = Some(Instant::now());
|
2023-06-14 22:42:23 -07:00
|
|
|
let screen = Screen::new(store)
|
|
|
|
.show_dialog(dialogstr)
|
|
|
|
.show_mode(modestr)
|
|
|
|
.borders(true)
|
|
|
|
.focus(focused);
|
2022-12-29 18:00:59 -08:00
|
|
|
f.render_stateful_widget(screen, area, sstate);
|
|
|
|
|
2023-04-28 16:52:33 -07:00
|
|
|
if hide_cursor {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
if let Some((cx, cy)) = sstate.get_term_cursor() {
|
|
|
|
if let Some(c) = cursor {
|
|
|
|
let style = Style::default().fg(Color::Green);
|
|
|
|
let span = Span::styled(c.to_string(), style);
|
|
|
|
let para = Paragraph::new(span);
|
|
|
|
let inner = Rect::new(cx, cy, 1, 1);
|
|
|
|
f.render_widget(para, inner)
|
|
|
|
}
|
|
|
|
f.set_cursor(cx, cy);
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn step(&mut self) -> Result<TerminalKey, std::io::Error> {
|
|
|
|
loop {
|
2023-09-10 16:45:27 +03:00
|
|
|
self.redraw(self.dirty, self.store.clone().lock().await.deref_mut())?;
|
|
|
|
self.dirty = false;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-01-23 17:08:11 -08:00
|
|
|
if !poll(Duration::from_secs(1))? {
|
|
|
|
// Redraw in case there's new messages to show.
|
2022-12-29 18:00:59 -08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
match read()? {
|
2024-03-20 22:13:47 -07:00
|
|
|
Event::Key(ke) => {
|
|
|
|
if ke.kind == KeyEventKind::Release {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(ke.into());
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
Event::Mouse(_) => {
|
|
|
|
// Do nothing for now.
|
|
|
|
},
|
2023-06-14 22:42:23 -07:00
|
|
|
Event::FocusGained => {
|
|
|
|
self.focused = true;
|
|
|
|
},
|
|
|
|
Event::FocusLost => {
|
|
|
|
self.focused = false;
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
|
|
|
Event::Resize(_, _) => {
|
|
|
|
// We'll redraw for the new size next time step() is called.
|
|
|
|
},
|
2023-01-28 18:01:17 -08:00
|
|
|
Event::Paste(s) => {
|
|
|
|
let act = InsertTextAction::Transcribe(s, MoveDir1D::Previous, 1.into());
|
|
|
|
let act = EditorAction::from(act);
|
|
|
|
let ctx = ProgramContext::default();
|
|
|
|
let mut store = self.store.lock().await;
|
|
|
|
|
|
|
|
match self.screen.editor_command(&act, &ctx, store.deref_mut()) {
|
|
|
|
Ok(None) => {},
|
|
|
|
Ok(Some(info)) => {
|
2023-04-28 16:52:33 -07:00
|
|
|
drop(store);
|
|
|
|
self.handle_info(info);
|
2023-01-28 18:01:17 -08:00
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
self.screen.push_error(e);
|
|
|
|
},
|
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_prepend(&mut self, acts: Vec<(ProgramAction, ProgramContext)>) {
|
|
|
|
let mut acts = VecDeque::from(acts);
|
|
|
|
acts.append(&mut self.actstack);
|
|
|
|
self.actstack = acts;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_pop(&mut self, keyskip: bool) -> Option<(ProgramAction, ProgramContext)> {
|
|
|
|
if let res @ Some(_) = self.actstack.pop_front() {
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
if keyskip {
|
|
|
|
return None;
|
|
|
|
} else {
|
|
|
|
return self.bindings.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
async fn action_run(
|
2022-12-29 18:00:59 -08:00
|
|
|
&mut self,
|
|
|
|
action: ProgramAction,
|
|
|
|
ctx: ProgramContext,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> IambResult<EditInfo> {
|
|
|
|
let info = match action {
|
|
|
|
// Do nothing.
|
|
|
|
Action::NoOp => None,
|
|
|
|
|
|
|
|
Action::Editor(act) => {
|
|
|
|
match self.screen.editor_command(&act, &ctx, store) {
|
|
|
|
Ok(info) => info,
|
|
|
|
Err(EditError::WrongBuffer(content)) if act.is_switchable(&ctx) => {
|
|
|
|
// Switch to the right window.
|
|
|
|
if let Some(winid) = content.to_window() {
|
|
|
|
let open = OpenTarget::Application(winid);
|
|
|
|
let open = WindowAction::Switch(open);
|
|
|
|
let _ = self.screen.window_command(&open, &ctx, store)?;
|
|
|
|
|
|
|
|
// Run command again.
|
|
|
|
self.screen.editor_command(&act, &ctx, store)?
|
|
|
|
} else {
|
|
|
|
return Err(EditError::WrongBuffer(content).into());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(err) => return Err(err.into()),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// Simple delegations.
|
2023-01-10 19:59:30 -08:00
|
|
|
Action::Application(act) => self.iamb_run(act, ctx, store).await?,
|
2022-12-29 18:00:59 -08:00
|
|
|
Action::CommandBar(act) => self.screen.command_bar(&act, &ctx)?,
|
|
|
|
Action::Macro(act) => self.bindings.macro_command(&act, &ctx, store)?,
|
|
|
|
Action::Scroll(style) => self.screen.scroll(&style, &ctx, store)?,
|
2023-04-28 16:52:33 -07:00
|
|
|
Action::ShowInfoMessage(info) => Some(info),
|
2022-12-29 18:00:59 -08:00
|
|
|
Action::Window(cmd) => self.screen.window_command(&cmd, &ctx, store)?,
|
|
|
|
|
|
|
|
Action::Jump(l, dir, count) => {
|
|
|
|
let count = ctx.resolve(&count);
|
|
|
|
let _ = self.screen.jump(l, dir, count, &ctx)?;
|
|
|
|
|
|
|
|
None
|
|
|
|
},
|
2023-04-28 16:52:33 -07:00
|
|
|
Action::Suspend => {
|
|
|
|
self.terminal.program_suspend()?;
|
|
|
|
|
|
|
|
None
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
// UI actions.
|
2023-06-28 23:42:31 -07:00
|
|
|
Action::Tab(cmd) => {
|
|
|
|
if let TabAction::Close(_, _) = &cmd {
|
|
|
|
self.last_layout = self.screen.as_description().into();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.screen.tab_command(&cmd, &ctx, store)?
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
Action::RedrawScreen => {
|
|
|
|
self.screen.clear_message();
|
|
|
|
self.redraw(true, store)?;
|
|
|
|
|
|
|
|
None
|
|
|
|
},
|
|
|
|
|
|
|
|
// Actions that create more Actions.
|
|
|
|
Action::Prompt(act) => {
|
|
|
|
let acts = self.screen.prompt(&act, &ctx, store)?;
|
|
|
|
self.action_prepend(acts);
|
|
|
|
|
|
|
|
None
|
|
|
|
},
|
|
|
|
Action::Command(act) => {
|
2023-03-01 18:46:33 -08:00
|
|
|
let acts = store.application.cmds.command(&act, &ctx)?;
|
2022-12-29 18:00:59 -08:00
|
|
|
self.action_prepend(acts);
|
|
|
|
|
|
|
|
None
|
|
|
|
},
|
|
|
|
Action::Repeat(rt) => {
|
|
|
|
self.bindings.repeat(rt, Some(ctx));
|
|
|
|
|
|
|
|
None
|
|
|
|
},
|
|
|
|
|
|
|
|
// Unimplemented.
|
|
|
|
Action::KeywordLookup => {
|
|
|
|
// XXX: implement
|
|
|
|
None
|
|
|
|
},
|
|
|
|
|
|
|
|
_ => {
|
|
|
|
// XXX: log unhandled actions? print message?
|
|
|
|
None
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
return Ok(info);
|
|
|
|
}
|
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
async fn iamb_run(
|
2022-12-29 18:00:59 -08:00
|
|
|
&mut self,
|
|
|
|
action: IambAction,
|
2023-01-04 12:51:33 -08:00
|
|
|
ctx: ProgramContext,
|
2022-12-29 18:00:59 -08:00
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> IambResult<EditInfo> {
|
2023-09-10 16:45:27 +03:00
|
|
|
if action.scribbles() {
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
let info = match action {
|
|
|
|
IambAction::ToggleScrollbackFocus => {
|
|
|
|
self.screen.current_window_mut()?.focus_toggle();
|
|
|
|
|
|
|
|
None
|
|
|
|
},
|
|
|
|
|
2023-03-04 12:23:17 -08:00
|
|
|
IambAction::Homeserver(act) => {
|
|
|
|
let acts = self.homeserver_command(act, ctx, store).await?;
|
|
|
|
self.action_prepend(acts);
|
|
|
|
|
|
|
|
None
|
|
|
|
},
|
2023-01-10 19:59:30 -08:00
|
|
|
IambAction::Message(act) => {
|
|
|
|
self.screen.current_window_mut()?.message_command(act, ctx, store).await?
|
|
|
|
},
|
2023-01-04 12:51:33 -08:00
|
|
|
IambAction::Room(act) => {
|
2023-01-10 19:59:30 -08:00
|
|
|
let acts = self.screen.current_window_mut()?.room_command(act, ctx, store).await?;
|
2023-01-04 12:51:33 -08:00
|
|
|
self.action_prepend(acts);
|
|
|
|
|
|
|
|
None
|
|
|
|
},
|
2023-01-10 19:59:30 -08:00
|
|
|
IambAction::Send(act) => {
|
|
|
|
self.screen.current_window_mut()?.send_command(act, ctx, store).await?
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
2023-01-10 19:59:30 -08:00
|
|
|
|
2023-10-07 18:24:25 -07:00
|
|
|
IambAction::OpenLink(url) => {
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
|
|
return open::that(url);
|
|
|
|
});
|
|
|
|
|
|
|
|
None
|
|
|
|
},
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
IambAction::Verify(act, user_dev) => {
|
|
|
|
if let Some(sas) = store.application.verifications.get(&user_dev) {
|
|
|
|
self.worker.verify(act, sas.clone())?
|
|
|
|
} else {
|
|
|
|
return Err(IambError::InvalidVerificationId(user_dev).into());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
IambAction::VerifyRequest(user_id) => {
|
|
|
|
if let Ok(user_id) = OwnedUserId::try_from(user_id.as_str()) {
|
|
|
|
self.worker.verify_request(user_id)?
|
|
|
|
} else {
|
|
|
|
return Err(IambError::InvalidUserId(user_id).into());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(info)
|
|
|
|
}
|
|
|
|
|
2023-03-04 12:23:17 -08:00
|
|
|
async fn homeserver_command(
|
|
|
|
&mut self,
|
|
|
|
action: HomeserverAction,
|
|
|
|
ctx: ProgramContext,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> IambResult<Vec<(Action<IambInfo>, ProgramContext)>> {
|
|
|
|
match action {
|
|
|
|
HomeserverAction::CreateRoom(alias, vis, flags) => {
|
|
|
|
let client = &store.application.worker.client;
|
2024-03-02 15:00:29 -08:00
|
|
|
let room_id = create_room(client, alias, vis, flags).await?;
|
2024-03-09 00:47:05 -08:00
|
|
|
let room = IambId::Room(room_id, None);
|
2023-03-04 12:23:17 -08:00
|
|
|
let target = OpenTarget::Application(room);
|
|
|
|
let action = WindowAction::Switch(target);
|
|
|
|
|
|
|
|
Ok(vec![(action.into(), ctx)])
|
|
|
|
},
|
2023-10-13 00:58:59 -05:00
|
|
|
HomeserverAction::Logout(user, true) => {
|
|
|
|
self.worker.logout(user)?;
|
|
|
|
let flags = CloseFlags::QUIT | CloseFlags::FORCE;
|
|
|
|
let act = TabAction::Close(TabTarget::All, flags);
|
|
|
|
|
|
|
|
Ok(vec![(act.into(), ctx)])
|
|
|
|
},
|
|
|
|
HomeserverAction::Logout(user, false) => {
|
|
|
|
let msg = "Would you like to logout?";
|
|
|
|
let act = IambAction::from(HomeserverAction::Logout(user, true));
|
|
|
|
let prompt = PromptYesNo::new(msg, vec![Action::from(act)]);
|
|
|
|
let prompt = Box::new(prompt);
|
|
|
|
|
|
|
|
Err(UIError::NeedConfirm(prompt))
|
|
|
|
},
|
2023-03-04 12:23:17 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-28 16:52:33 -07:00
|
|
|
fn handle_info(&mut self, info: InfoMessage) {
|
|
|
|
match info {
|
|
|
|
InfoMessage::Message(info) => {
|
|
|
|
self.screen.push_info(info);
|
|
|
|
},
|
|
|
|
InfoMessage::Pager(text) => {
|
|
|
|
let pager = Box::new(Pager::new(text, vec![]));
|
|
|
|
self.bindings.run_dialog(pager);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
pub async fn run(&mut self) -> Result<(), std::io::Error> {
|
|
|
|
self.terminal.clear()?;
|
|
|
|
|
|
|
|
let store = self.store.clone();
|
|
|
|
|
|
|
|
while self.screen.tabs() != 0 {
|
|
|
|
let key = self.step().await?;
|
|
|
|
|
|
|
|
self.bindings.input_key(key);
|
|
|
|
|
|
|
|
let mut locked = store.lock().await;
|
|
|
|
let mut keyskip = false;
|
|
|
|
|
|
|
|
while let Some((action, ctx)) = self.action_pop(keyskip) {
|
2023-01-10 19:59:30 -08:00
|
|
|
match self.action_run(action, ctx, locked.deref_mut()).await {
|
2022-12-29 18:00:59 -08:00
|
|
|
Ok(None) => {
|
|
|
|
// Continue processing.
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
Ok(Some(info)) => {
|
2023-04-28 16:52:33 -07:00
|
|
|
self.handle_info(info);
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
// Continue processing; we'll redraw later.
|
|
|
|
continue;
|
|
|
|
},
|
2023-04-28 16:52:33 -07:00
|
|
|
Err(
|
|
|
|
UIError::NeedConfirm(dialog) |
|
|
|
|
UIError::EditingFailure(EditError::NeedConfirm(dialog)),
|
|
|
|
) => {
|
|
|
|
self.bindings.run_dialog(dialog);
|
|
|
|
continue;
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
Err(e) => {
|
|
|
|
self.screen.push_error(e);
|
|
|
|
|
|
|
|
// Skip processing any more keypress Actions until the next key.
|
|
|
|
keyskip = true;
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-28 23:42:31 -07:00
|
|
|
if let Some(ref layout) = self.last_layout {
|
|
|
|
let locked = self.store.lock().await;
|
|
|
|
let path = locked.application.settings.layout_json.as_path();
|
|
|
|
path.parent().map(create_dir_all).transpose()?;
|
|
|
|
|
|
|
|
let file = File::create(path)?;
|
|
|
|
let writer = BufWriter::new(file);
|
|
|
|
|
|
|
|
if let Err(e) = serde_json::to_writer(writer, layout) {
|
|
|
|
tracing::error!("Failed to save window layout while exiting: {}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
crossterm::terminal::disable_raw_mode()?;
|
|
|
|
execute!(self.terminal.backend_mut(), LeaveAlternateScreen)?;
|
|
|
|
self.terminal.show_cursor()?;
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
fn gen_passphrase() -> String {
|
|
|
|
rand::thread_rng()
|
|
|
|
.sample_iter(&Alphanumeric)
|
|
|
|
.take(20)
|
|
|
|
.map(char::from)
|
|
|
|
.collect()
|
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
fn read_response(question: &str) -> String {
|
|
|
|
println!("{question}");
|
|
|
|
let mut input = String::new();
|
|
|
|
let _ = std::io::stdin().read_line(&mut input);
|
|
|
|
input
|
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
fn read_yesno(question: &str) -> Option<char> {
|
|
|
|
read_response(question).chars().next().map(|c| c.to_ascii_lowercase())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn login(worker: &Requester, settings: &ApplicationSettings) -> IambResult<()> {
|
|
|
|
if settings.session_json.is_file() {
|
|
|
|
let session = settings.read_session(&settings.session_json)?;
|
|
|
|
worker.login(LoginStyle::SessionRestore(session.into()))?;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
if settings.session_json_old.is_file() && !settings.sled_dir.is_dir() {
|
|
|
|
let session = settings.read_session(&settings.session_json_old)?;
|
|
|
|
worker.login(LoginStyle::SessionRestore(session.into()))?;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
return Ok(());
|
|
|
|
}
|
2023-11-04 17:39:17 -04:00
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
loop {
|
|
|
|
let login_style =
|
|
|
|
match read_response("Please select login type: [p]assword / [s]ingle sign on")
|
|
|
|
.chars()
|
|
|
|
.next()
|
|
|
|
.map(|c| c.to_ascii_lowercase())
|
|
|
|
{
|
|
|
|
None | Some('p') => {
|
|
|
|
let password = rpassword::prompt_password("Password: ")?;
|
|
|
|
LoginStyle::Password(password)
|
|
|
|
},
|
|
|
|
Some('s') => LoginStyle::SingleSignOn,
|
|
|
|
Some(_) => {
|
|
|
|
println!("Failed to login. Please enter 'p' or 's'");
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
};
|
2023-11-04 17:39:17 -04:00
|
|
|
|
|
|
|
match worker.login(login_style) {
|
2022-12-29 18:00:59 -08:00
|
|
|
Ok(info) => {
|
|
|
|
if let Some(msg) = info {
|
2023-01-30 13:51:32 -08:00
|
|
|
println!("{msg}");
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
},
|
|
|
|
Err(err) => {
|
2023-01-30 13:51:32 -08:00
|
|
|
println!("Failed to login: {err}");
|
2022-12-29 18:00:59 -08:00
|
|
|
continue;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn print_exit<T: Display, N>(v: T) -> N {
|
2024-03-23 19:09:11 -07:00
|
|
|
eprintln!("{v}");
|
2022-12-29 18:00:59 -08:00
|
|
|
process::exit(2);
|
|
|
|
}
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
// We can't access the OlmMachine directly, so write the keys to a temporary
|
|
|
|
// file first, and then import them later.
|
|
|
|
async fn check_import_keys(
|
|
|
|
settings: &ApplicationSettings,
|
|
|
|
) -> IambResult<Option<(temp_dir::TempDir, String)>> {
|
|
|
|
let do_import = settings.sled_dir.is_dir() && !settings.sqlite_dir.is_dir();
|
|
|
|
|
|
|
|
if !do_import {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
|
|
|
let question = format!(
|
|
|
|
"Found old sled store in {}. Would you like to export room keys from it? [y]es/[n]o",
|
|
|
|
settings.sled_dir.display()
|
|
|
|
);
|
|
|
|
|
|
|
|
loop {
|
|
|
|
match read_yesno(&question) {
|
|
|
|
Some('y') => {
|
|
|
|
break;
|
|
|
|
},
|
|
|
|
Some('n') => {
|
|
|
|
return Ok(None);
|
|
|
|
},
|
|
|
|
Some(_) | None => {
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let keys = sled_export::export_room_keys(&settings.sled_dir).await?;
|
|
|
|
let passphrase = gen_passphrase();
|
|
|
|
|
|
|
|
println!("* Encrypting {} room keys with the passphrase {passphrase:?}...", keys.len());
|
|
|
|
|
|
|
|
let encrypted = match encrypt_room_key_export(&keys, &passphrase, 500000) {
|
|
|
|
Ok(encrypted) => encrypted,
|
|
|
|
Err(e) => {
|
|
|
|
format!("* Failed to encrypt room keys during export: {e}");
|
|
|
|
process::exit(2);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let tmpdir = TempDir::new()?;
|
|
|
|
let exported = tmpdir.child("keys");
|
|
|
|
|
|
|
|
println!("* Writing encrypted room keys to {}...", exported.display());
|
|
|
|
tokio::fs::write(&exported, &encrypted).await?;
|
|
|
|
|
|
|
|
Ok(Some((tmpdir, passphrase)))
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn login_upgrade(
|
|
|
|
keydir: TempDir,
|
|
|
|
passphrase: String,
|
|
|
|
worker: &Requester,
|
|
|
|
settings: &ApplicationSettings,
|
|
|
|
store: &AsyncProgramStore,
|
|
|
|
) -> IambResult<()> {
|
|
|
|
println!(
|
|
|
|
"Please log in for {} to import the room keys into a new session",
|
|
|
|
settings.profile.user_id
|
|
|
|
);
|
|
|
|
|
|
|
|
login(worker, settings).await?;
|
|
|
|
|
|
|
|
println!("* Importing room keys...");
|
|
|
|
|
|
|
|
let exported = keydir.child("keys");
|
|
|
|
let imported = worker.client.encryption().import_room_keys(exported, &passphrase).await;
|
|
|
|
|
|
|
|
match imported {
|
|
|
|
Ok(res) => {
|
|
|
|
println!(
|
|
|
|
"* Successfully imported {} out of {} keys",
|
|
|
|
res.imported_count, res.total_count
|
|
|
|
);
|
|
|
|
let _ = keydir.cleanup();
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
println!(
|
|
|
|
"Failed to import room keys from {}/keys: {e}\n\n\
|
|
|
|
They have been encrypted with the passphrase {passphrase:?}.\
|
|
|
|
Please save them and try importing them manually instead\n",
|
|
|
|
keydir.path().display()
|
|
|
|
);
|
|
|
|
|
|
|
|
loop {
|
|
|
|
match read_yesno("Would you like to continue logging in? [y]es/[n]o") {
|
|
|
|
Some('y') => break,
|
|
|
|
Some('n') => print_exit("* Exiting..."),
|
|
|
|
Some(_) | None => continue,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
println!("* Syncing...");
|
2024-03-23 19:09:11 -07:00
|
|
|
worker::do_first_sync(&worker.client, store)
|
|
|
|
.await
|
|
|
|
.map_err(IambError::from)?;
|
2024-03-02 15:00:29 -08:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn login_normal(
|
|
|
|
worker: &Requester,
|
|
|
|
settings: &ApplicationSettings,
|
|
|
|
store: &AsyncProgramStore,
|
|
|
|
) -> IambResult<()> {
|
|
|
|
println!("* Logging in for {}...", settings.profile.user_id);
|
|
|
|
login(worker, settings).await?;
|
2024-03-23 19:09:11 -07:00
|
|
|
worker::do_first_sync(&worker.client, store)
|
|
|
|
.await
|
|
|
|
.map_err(IambError::from)?;
|
2024-03-02 15:00:29 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
async fn run(settings: ApplicationSettings) -> IambResult<()> {
|
2024-03-02 15:00:29 -08:00
|
|
|
// Get old keys the first time we run w/ the upgraded SDK.
|
|
|
|
let import_keys = check_import_keys(&settings).await?;
|
|
|
|
|
|
|
|
// Set up client state.
|
|
|
|
create_dir_all(settings.sqlite_dir.as_path())?;
|
|
|
|
let client = worker::create_client(&settings).await;
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
// Set up the async worker thread and global store.
|
2024-03-02 15:00:29 -08:00
|
|
|
let worker = ClientWorker::spawn(client.clone(), settings.clone()).await;
|
2022-12-29 18:00:59 -08:00
|
|
|
let store = ChatStore::new(worker.clone(), settings.clone());
|
|
|
|
let store = Store::new(store);
|
|
|
|
let store = Arc::new(AsyncMutex::new(store));
|
|
|
|
worker.init(store.clone());
|
|
|
|
|
2024-03-23 19:09:11 -07:00
|
|
|
let res = if let Some((keydir, pass)) = import_keys {
|
|
|
|
login_upgrade(keydir, pass, &worker, &settings, &store).await
|
2024-03-02 15:00:29 -08:00
|
|
|
} else {
|
2024-03-23 19:09:11 -07:00
|
|
|
login_normal(&worker, &settings, &store).await
|
|
|
|
};
|
|
|
|
|
|
|
|
match res {
|
|
|
|
Err(UIError::Application(IambError::Matrix(e))) => {
|
|
|
|
if let Some(ErrorKind::UnknownToken { .. }) = e.client_api_error_kind() {
|
|
|
|
print_exit("Server did not recognize our API token; did you log out from this session elsewhere?")
|
|
|
|
} else {
|
|
|
|
print_exit(e)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => print_exit(e),
|
|
|
|
Ok(()) => (),
|
2024-03-02 15:00:29 -08:00
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-06-14 22:42:23 -07:00
|
|
|
fn restore_tty() {
|
2022-12-29 18:00:59 -08:00
|
|
|
let _ = crossterm::terminal::disable_raw_mode();
|
2023-01-28 18:01:17 -08:00
|
|
|
let _ = crossterm::execute!(stdout(), DisableBracketedPaste);
|
2023-06-14 22:42:23 -07:00
|
|
|
let _ = crossterm::execute!(stdout(), DisableFocusChange);
|
2022-12-29 18:00:59 -08:00
|
|
|
let _ = crossterm::execute!(stdout(), LeaveAlternateScreen);
|
|
|
|
let _ = crossterm::execute!(stdout(), CursorShow);
|
2023-06-14 22:42:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure panics clean up the terminal properly.
|
|
|
|
let orig_hook = std::panic::take_hook();
|
|
|
|
std::panic::set_hook(Box::new(move |panic_info| {
|
|
|
|
restore_tty();
|
2022-12-29 18:00:59 -08:00
|
|
|
orig_hook(panic_info);
|
|
|
|
process::exit(1);
|
|
|
|
}));
|
|
|
|
|
|
|
|
let mut application = Application::new(settings, store).await?;
|
|
|
|
|
|
|
|
// We can now run the application.
|
|
|
|
application.run().await?;
|
2023-06-14 22:42:23 -07:00
|
|
|
restore_tty();
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> IambResult<()> {
|
|
|
|
// Parse command-line flags.
|
|
|
|
let iamb = Iamb::parse();
|
|
|
|
|
|
|
|
// Load configuration and set up the Matrix SDK.
|
|
|
|
let settings = ApplicationSettings::load(iamb).unwrap_or_else(print_exit);
|
|
|
|
|
2023-07-07 20:34:52 -07:00
|
|
|
// Set umask on Unix platforms so that tokens, keys, etc. are only readable by the user.
|
|
|
|
#[cfg(unix)]
|
|
|
|
unsafe {
|
|
|
|
libc::umask(0o077);
|
|
|
|
};
|
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
// Set up the tracing subscriber so we can log client messages.
|
|
|
|
let log_prefix = format!("iamb-log-{}", settings.profile_name);
|
|
|
|
let log_dir = settings.dirs.logs.as_path();
|
|
|
|
|
|
|
|
let appender = tracing_appender::rolling::daily(log_dir, log_prefix);
|
|
|
|
let (appender, guard) = tracing_appender::non_blocking(appender);
|
|
|
|
|
|
|
|
let subscriber = FmtSubscriber::builder()
|
|
|
|
.with_writer(appender)
|
2023-03-13 16:10:28 -07:00
|
|
|
.with_max_level(settings.tunables.log_level)
|
2023-01-10 19:59:30 -08:00
|
|
|
.finish();
|
|
|
|
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
|
|
|
|
|
|
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
|
|
|
.enable_all()
|
2023-07-05 15:25:42 -07:00
|
|
|
.worker_threads(2)
|
2023-01-10 19:59:30 -08:00
|
|
|
.thread_name_fn(|| {
|
|
|
|
static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst);
|
2023-01-30 13:51:32 -08:00
|
|
|
format!("iamb-worker-{id}")
|
2023-01-10 19:59:30 -08:00
|
|
|
})
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
rt.block_on(async move { run(settings).await })?;
|
|
|
|
|
|
|
|
drop(guard);
|
2022-12-29 18:00:59 -08:00
|
|
|
process::exit(0);
|
2021-07-02 23:22:37 -07:00
|
|
|
}
|