Compare commits

...

1 Commits

Author SHA1 Message Date
Benedikt Peetz 52b77aee4b
Todo: Rebase me! 2023-07-15 23:30:58 +02:00
18 changed files with 379 additions and 141 deletions

10
Cargo.lock generated
View File

@ -415,6 +415,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.3" version = "0.9.3"
@ -1291,6 +1300,7 @@ dependencies = [
name = "lua_macros" name = "lua_macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"convert_case",
"proc-macro2 1.0.64", "proc-macro2 1.0.64",
"quote 1.0.29", "quote 1.0.29",
"syn 2.0.25", "syn 2.0.25",

View File

@ -45,10 +45,14 @@
overlays = [(import rust-overlay)]; overlays = [(import rust-overlay)];
}; };
#rust-nightly = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default); nightly = true;
rust-stable = pkgs.rust-bin.stable.latest.default;
craneLib = (crane.mkLib pkgs).overrideToolchain rust-stable; rust =
if nightly
then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default)
else pkgs.rust-bin.stable.latest.default;
craneLib = (crane.mkLib pkgs).overrideToolchain rust;
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
pkg-config pkg-config
@ -78,7 +82,7 @@
statix statix
ltex-ls ltex-ls
rust-stable rust
rust-analyzer rust-analyzer
cargo-edit cargo-edit
cargo-expand cargo-expand

View File

@ -4,9 +4,10 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[lib] [lib]
crate_type = ["proc-macro"] proc-macro = true
[dependencies] [dependencies]
convert_case = "0.6.0"
proc-macro2 = "1.0.64" proc-macro2 = "1.0.64"
quote = "1.0.29" quote = "1.0.29"
syn = "2.0.25" syn = { version = "2.0.25", features = ["extra-traits", "full", "parsing"] }

View File

@ -1,59 +1,46 @@
mod struct_to_ci_enum;
mod mark_as_ci_command;
use mark_as_ci_command::generate_final_function;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote}; use quote::quote;
use syn; use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function};
use syn::{self, ItemFn};
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn generate_ci_functions(_: TokenStream, input: TokenStream) -> TokenStream { pub fn turn_struct_to_ci_commands(_attrs: TokenStream, input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree // Construct a representation of Rust code as a syntax tree
// that we can manipulate // that we can manipulate
let input = syn::parse(input) let input = syn::parse(input)
.expect("This should always be valid rust code, as it's extracted from direct code"); .expect("This should always be valid rust code, as it's extracted from direct code");
// Build the trait implementation // Build the trait implementation
generate_generate_ci_functions(&input) let generate_ci_function: TokenStream2 = generate_generate_ci_function(&input);
}
let command_enum = generate_command_enum(&input);
fn generate_generate_ci_functions(input: &syn::DeriveInput) -> TokenStream {
let input_tokens: TokenStream2 = match &input.data {
syn::Data::Struct(input) => match &input.fields {
syn::Fields::Named(named_fields) => named_fields
.named
.iter()
.map(|field| -> TokenStream2 {
let field_ident = field.ident.as_ref().expect(
"These are only the named field, thus they all should have a name.",
);
let function_name_ident = format_ident!("fun_{}", field_ident);
let function_name = format!("{}", field_ident);
quote! { quote! {
let #function_name_ident = context.create_function(#field_ident).expect( #command_enum
&format!( #generate_ci_function
"The function: `{}` should be defined", }
#function_name .into()
) }
);
globals.set(#function_name, #function_name_ident).expect( /// Turn a function into a valid ci command function
&format!( #[proc_macro_attribute]
"Setting a static global value ({}, fun_{}) should work", pub fn ci_command(_attrs: TokenStream, input: TokenStream) -> TokenStream {
#function_name, // Construct a representation of Rust code as a syntax tree
#function_name // that we can manipulate
) let mut input: ItemFn = syn::parse(input)
); .expect("This should always be valid rust code, as it's extracted from direct code");
// Build the trait implementation
let output_function: TokenStream2 = generate_final_function(&mut input);
//panic!("{:#?}", output_function);
quote! {
#output_function
} }
.into() .into()
})
.collect(),
_ => unimplemented!("Only implemented for named fileds"),
},
_ => unimplemented!("Only for implemented for structs"),
};
let gen = quote! {
pub fn generate_ci_functions(context: &mut Context) {
let globals = context.globals();
#input_tokens
}
};
gen.into()
} }

View File

@ -0,0 +1,52 @@
use convert_case::{Case, Casing};
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote, ToTokens};
use syn::{
braced,
punctuated::Punctuated,
token::{Comma, Semi},
Block, Expr, FnArg, Stmt, Token,
};
pub fn generate_final_function(input: &mut syn::ItemFn) -> TokenStream2 {
append_tx_send_code(input);
let output: TokenStream2 = syn::parse(input.into_token_stream().into())
.expect("This is generated from valid rust code, it should stay that way.");
output
}
fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn {
let function_name_pascal = format_ident!(
"{}",
input
.sig
.ident
.clone()
.to_string()
.from_case(Case::Snake)
.to_case(Case::Pascal)
);
let tx_send = quote! {
{
let tx = context.named_registry_value("sender_for_ci_commands");
tx
.send(Event::CommandEvent(Commands::#function_name_pascal))
.expect("This should work, as the reciever is not dropped");
}
};
let content;
braced!(content in tx_send);
let mut tx_send_expr: Vec<Stmt> =
Block::parse_within(content).expect("This is a static string, it will always parse");
let mut new_stmts: Vec<Stmt> = Vec::with_capacity(input.block.stmts.len() + tx_send_expr.len());
new_stmts.append(&mut tx_send_expr);
new_stmts.append(&mut input.block.stmts);
input.block.stmts = new_stmts;
input
}

View File

@ -0,0 +1,111 @@
use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{self, ReturnType};
pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 {
let input_tokens: TokenStream2 = match &input.data {
syn::Data::Struct(input) => match &input.fields {
syn::Fields::Named(named_fields) => named_fields
.named
.iter()
.map(|field| -> TokenStream2 {
let field_ident = field.ident.as_ref().expect(
"These are only the named field, thus they all should have a name.",
);
let function_name_ident = format_ident!("fun_{}", field_ident);
let function_name = format!("{}", field_ident);
quote! {
let #function_name_ident = context.create_function(#field_ident).expect(
&format!(
"The function: `{}` should be defined",
#function_name
)
);
globals.set(#function_name, #function_name_ident).expect(
&format!(
"Setting a static global value ({}, fun_{}) should work",
#function_name,
#function_name
)
);
}
.into()
})
.collect(),
_ => unimplemented!("Only implemented for named fileds"),
},
_ => unimplemented!("Only implemented for structs"),
};
let gen = quote! {
pub fn generate_ci_functions(
context: &mut rlua::Context,
tx: std::sync::mpsc::Sender<crate::app::events::event_types::Event>)
{
let globals = context.globals();
#input_tokens
}
};
gen.into()
}
pub fn generate_command_enum(input: &syn::DeriveInput) -> TokenStream2 {
let input_tokens: TokenStream2 = match &input.data {
syn::Data::Struct(input) => match &input.fields {
syn::Fields::Named(named_fields) => named_fields
.named
.iter()
.map(|field| -> TokenStream2 {
let field_ident = field
.ident
.as_ref()
.expect("These are only the named field, thus they all should have a name.");
let enum_variant_type = match &field.ty {
syn::Type::BareFn(function) => {
let return_path: &ReturnType = &function.output;
match return_path {
ReturnType::Default => None,
ReturnType::Type(_, return_type) => Some(match *(return_type.to_owned()) {
syn::Type::Path(path_type) => path_type
.path
.get_ident()
.expect("A path should either be complete, or only conain one segment")
.to_owned(),
_ => unimplemented!("This is only implemented for path types"),
}),
}
}
_ => unimplemented!("This is only implemented for bare function types"),
};
let enum_variant_name = format_ident!(
"{}",
field_ident.to_string().from_case(Case::Snake).to_case(Case::Pascal)
);
if enum_variant_type.is_some() {
quote! {
#enum_variant_name (#enum_variant_type),
}
.into()
} else {
quote! {
#enum_variant_name,
}
}
})
.collect(),
_ => unimplemented!("Only implemented for named fileds"),
},
_ => unimplemented!("Only implemented for structs"),
};
let gen = quote! {
pub enum Commands {
#input_tokens
}
};
gen.into()
}

View File

@ -1,25 +0,0 @@
use anyhow::Result;
use tokio::sync::mpsc;
use super::events::event_types::Event;
#[derive(Debug, Clone)]
pub enum Command {
// Closes the application
Exit,
CommandLineShow,
CommandLineHide,
CyclePlanes,
CyclePlanesRev,
// sends a message to the current room
RoomMessageSend(String),
}
pub async fn execute(channel: &mpsc::Sender<Event>, command: Command) -> Result<()> {
let event = Event::CommandEvent(command);
channel.send(event).await?;
Ok(())
}

View File

@ -1,13 +1,47 @@
use lua_macros::generate_ci_functions; use lua_macros::{turn_struct_to_ci_commands, ci_command};
use rlua::Context; use rlua::Context;
// This struct is here to gurantee, that all functions actually end up in the lua context. use super::events::event_types::Event;
// I. e. rust should throw a compile error, when one field is added, but not a matching function.
#[generate_ci_functions()] /// This struct is here to guarantee, that all functions actually end up in the lua context.
/// I.e. Rust should throw a compile error, when one field is added, but not a matching function.
///
/// What it does:
/// - Generates a `generate_ci_functions` function, which wraps the specified rust in functions
/// in lua and exports them to the globals in the context provided as argument.
/// - Generates a Commands enum, which contains every Camel cased version of the fields.
///
/// Every command specified here should have a function named $command_name, where $command_name is the snake cased name of the field.
///
/// This function is exported to the lua context, thus it's signature must be:
/// ```rust
/// fn $command_name(context: Context, input_string: String) -> Result<$return_type, rlua::Error> {}
/// ```
/// where $return_type is the type returned by the function (the only supported ones are right now
/// `String` and `()`).
#[turn_struct_to_ci_commands]
struct Commands<'lua> { struct Commands<'lua> {
greet: Function<'lua>, greet: fn(usize) -> String,
// Closes the application
exit: fn(),
//command_line_show: fn(),
//command_line_hide: fn(),
//cycle_planes: fn(),
//cycle_planes_rev: fn(),
//// sends a message to the current room
//room_message_send: fn(String),
} }
fn greet(context: Context, name: String) -> Result<String, rlua::Error> { #[ci_command]
fn greet(_context: Context, name: String) -> Result<String, rlua::Error> {
Ok(format!("Name is {}", name)) Ok(format!("Name is {}", name))
} }
#[ci_command]
fn exit(_context: Context, _input_str: String) -> Result<(), rlua::Error> {
Event::CommandEvent(Commands::Exit);
Ok(())
}

View File

@ -0,0 +1,15 @@
use anyhow::{bail, Context, Result};
use cli_log::warn;
use crate::app::{events::event_types::EventStatus, App};
pub async fn handle(app: &mut App<'_>, output: String) -> Result<EventStatus> {
info!("Recieved command output: `{}`");
if let Some(cli) = app.ui.cli {
cli.
} else {
warn!("There is no way to display the output!");
}
Ok(EventStatus::Ok)
}

View File

@ -5,32 +5,30 @@ use cli_log::info;
pub async fn handle(app: &mut App<'_>, command: &Command) -> Result<EventStatus> { pub async fn handle(app: &mut App<'_>, command: &Command) -> Result<EventStatus> {
info!("Handling command: {:#?}", command); info!("Handling command: {:#?}", command);
Ok(match command { match command {
Command::Exit => EventStatus::Terminate, Command::Exit => return Ok(EventStatus::Terminate),
Command::CommandLineShow => { Command::CommandLineShow => {
app.ui.cli_enable(); app.ui.cli_enable();
EventStatus::Ok
} }
Command::CommandLineHide => { Command::CommandLineHide => {
app.ui.cli_disable(); app.ui.cli_disable();
EventStatus::Ok
} }
Command::CyclePlanes => { Command::CyclePlanes => {
app.ui.cycle_main_input_position(); app.ui.cycle_main_input_position();
EventStatus::Ok
} }
Command::CyclePlanesRev => { Command::CyclePlanesRev => {
app.ui.cycle_main_input_position_rev(); app.ui.cycle_main_input_position_rev();
EventStatus::Ok
} }
Command::RoomMessageSend(msg) => { Command::RoomMessageSend(msg) => {
if let Some(room) = app.status.room_mut() { if let Some(room) = app.status.room_mut() {
room.send(msg.clone()).await?; room.send(msg.clone()).await?;
} else {
// FIXME: This needs a callback to return an error.
} }
EventStatus::Ok
} }
}) }
Ok(EventStatus::Ok)
} }

View File

@ -0,0 +1,22 @@
use anyhow::{Context, Result};
use cli_log::info;
use crate::app::{App, events::event_types::{EventStatus, Event}};
pub async fn handle(app: &mut App<'_>, command: &str) -> Result<EventStatus> {
info!("Recieved ci command: `{command}`; executing..");
// TODO: Should the ci support more than strings?
let output = app.lua.context(|context| -> Result<String> {
let output = context
.load(&command)
.eval::<String>()
.with_context(|| format!("Failed to execute: `{command}`"))?;
info!("Function evaluated to: `{output}`");
Ok(output)
})?;
app.transmitter.send(Event::CiOutput(output));
Ok(EventStatus::Ok)
}

View File

@ -2,7 +2,7 @@ use anyhow::Result;
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers}; use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers};
use crate::{ use crate::{
app::{command, command::Command, events::event_types::EventStatus, App}, app::{events::event_types::EventStatus, App},
ui::central, ui::central,
}; };
@ -11,7 +11,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
CrosstermEvent::Key(KeyEvent { CrosstermEvent::Key(KeyEvent {
code: KeyCode::Esc, .. code: KeyCode::Esc, ..
}) => { }) => {
command::execute(app.channel_tx(), Command::Exit).await?; app.transmitter.send(Event::CommandEvent(Command::Exit)).await?;
} }
CrosstermEvent::Key(KeyEvent { CrosstermEvent::Key(KeyEvent {
code: KeyCode::Tab, .. code: KeyCode::Tab, ..
@ -153,7 +153,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
"There can only be one line in the buffer, as we collect it on enter being inputted" "There can only be one line in the buffer, as we collect it on enter being inputted"
) )
.to_owned(); .to_owned();
let output = app.handle_ci_event(&cli_event).await?; let output = (&cli_event).await?;
// delete the old text: // delete the old text:

View File

@ -1,4 +1,6 @@
pub mod command; pub mod command;
pub mod lua_command;
pub mod main; pub mod main;
pub mod matrix; pub mod matrix;
pub mod setup; pub mod setup;
pub mod ci_output;

View File

@ -5,7 +5,7 @@ use crossterm::event::Event as CrosstermEvent;
use crate::app::{command::Command, status::State, App}; use crate::app::{command::Command, status::State, App};
use self::handlers::{command, main, matrix, setup}; use self::handlers::{command, lua_command, main, matrix, setup};
use super::EventStatus; use super::EventStatus;
@ -14,11 +14,21 @@ pub enum Event {
InputEvent(CrosstermEvent), InputEvent(CrosstermEvent),
MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse), MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse),
CommandEvent(Command), CommandEvent(Command),
LuaCommand(String),
CiOutput(String),
} }
impl Event { impl Event {
pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> { pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> {
match &self { match &self {
Event::LuaCommand(command) => lua_command::handle(app, command)
.await
.with_context(|| format!("Failed to handle lua command: `{:#?}`", command)),
Event::CiOutput(output) => ci_output::handle(app, output)
.await
.with_context(|| format!("Failed to handle ci output: `{:#?}`", output)),
Event::MatrixEvent(event) => matrix::handle(app, event) Event::MatrixEvent(event) => matrix::handle(app, event)
.await .await
.with_context(|| format!("Failed to handle matrix event: `{:#?}`", event)), .with_context(|| format!("Failed to handle matrix event: `{:#?}`", event)),

View File

@ -1,6 +1,11 @@
#[derive(Debug)] #[derive(Debug)]
pub enum EventStatus { pub enum EventStatus {
/// The event was handled successfully
Ok, Ok,
/// TODO
Finished, Finished,
/// Terminate the whole application
Terminate, Terminate,
} }

View File

@ -1,30 +1,32 @@
pub mod command;
pub mod command_interface; pub mod command_interface;
pub mod events; pub mod events;
pub mod status; pub mod status;
pub mod transmitter;
use std::path::Path; use std::path::Path;
use accounts::{Account, AccountsManager};
use anyhow::{Context, Error, Result}; use anyhow::{Context, Error, Result};
use cli_log::info; use cli_log::info;
use matrix_sdk::Client; use matrix_sdk::Client;
use rlua::Lua; use rlua::Lua;
use status::{State, Status}; use status::{State, Status};
use tokio::sync::mpsc; use tokio::sync::mpsc::Sender;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use crate::{accounts, app::command_interface::generate_ci_functions, ui::{central, setup}}; use crate::{
accounts::{Account, AccountsManager},
app::{command_interface::generate_ci_functions, events::event_types::Event},
ui::{central, setup},
};
use self::events::event_types::{self, Event}; use self::{events::event_types, transmitter::Transmitter};
pub struct App<'ui> { pub struct App<'ui> {
ui: central::UI<'ui>, ui: central::UI<'ui>,
accounts_manager: accounts::AccountsManager, accounts_manager: AccountsManager,
status: Status, status: Status,
channel_tx: mpsc::Sender<event_types::Event>, transmitter: Transmitter,
channel_rx: mpsc::Receiver<event_types::Event>,
input_listener_killer: CancellationToken, input_listener_killer: CancellationToken,
matrix_listener_killer: CancellationToken, matrix_listener_killer: CancellationToken,
@ -33,11 +35,11 @@ pub struct App<'ui> {
impl App<'_> { impl App<'_> {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
fn set_up_lua() -> Lua { fn set_up_lua(tx: Sender<Event>) -> Lua {
let lua = Lua::new(); let lua = Lua::new();
lua.context(|mut lua_context| { lua.context(|mut lua_context| {
generate_ci_functions(&mut lua_context); generate_ci_functions(&mut lua_context, tx);
}); });
lua lua
} }
@ -50,41 +52,24 @@ impl App<'_> {
None None
}; };
let (channel_tx, channel_rx) = mpsc::channel(256); let transmitter = Transmitter::new();
Ok(Self { Ok(Self {
ui: central::UI::new()?, ui: central::UI::new()?,
accounts_manager: AccountsManager::new(config)?, accounts_manager: AccountsManager::new(config)?,
status: Status::new(None), status: Status::new(None),
channel_tx, transmitter,
channel_rx,
input_listener_killer: CancellationToken::new(), input_listener_killer: CancellationToken::new(),
matrix_listener_killer: CancellationToken::new(), matrix_listener_killer: CancellationToken::new(),
lua: set_up_lua(), lua: set_up_lua(transmitter.tx()),
}) })
} }
pub async fn handle_ci_event(&self, event: &str) -> Result<String> {
info!("Recieved ci event: `{event}`; executing..");
// TODO: Should the ci support more than strings?
let output = self.lua.context(|context| -> Result<String> {
let output = context
.load(&event)
.eval::<String>()
.with_context(|| format!("Failed to execute: `{event}`"))?;
info!("Function evaluated to: `{output}`");
Ok(output)
})?;
Ok(output)
}
pub async fn run(&mut self) -> Result<()> { pub async fn run(&mut self) -> Result<()> {
// Spawn input event listener // Spawn input event listener
tokio::task::spawn(events::poll_input_events( tokio::task::spawn(events::poll_input_events(
self.channel_tx.clone(), self.transmitter.tx(),
self.input_listener_killer.clone(), self.input_listener_killer.clone(),
)); ));
@ -100,15 +85,16 @@ impl App<'_> {
self.status.set_state(State::Main); self.status.set_state(State::Main);
self.ui.update(&self.status).await?; self.ui.update(&self.status).await?;
let event: event_types::Event = match self.channel_rx.recv().await { let event = self
Some(e) => e, .transmitter
None => return Err(Error::msg("Event channel has no senders")), .recv()
}; .await
.context("Failed to get next event")?;
match event.handle(self).await? { match event.handle(self).await? {
event_types::EventStatus::Ok => (), event_types::EventStatus::Ok => (),
event_types::EventStatus::Finished => (),
event_types::EventStatus::Terminate => break, event_types::EventStatus::Terminate => break,
_ => (),
}; };
} }
@ -123,10 +109,11 @@ impl App<'_> {
self.status.set_state(State::Setup); self.status.set_state(State::Setup);
self.ui.update_setup().await?; self.ui.update_setup().await?;
let event: event_types::Event = match self.channel_rx.recv().await { let event = self
Some(e) => e, .transmitter
None => return Err(Error::msg("Event channel has no senders")), .recv()
}; .await
.context("Failed to get next event")?;
match event.handle(self).await? { match event.handle(self).await? {
event_types::EventStatus::Ok => (), event_types::EventStatus::Ok => (),
@ -150,7 +137,7 @@ impl App<'_> {
// Spawn Matrix Event Listener // Spawn Matrix Event Listener
tokio::task::spawn(events::poll_matrix_events( tokio::task::spawn(events::poll_matrix_events(
self.channel_tx.clone(), self.transmitter.tx(),
self.matrix_listener_killer.clone(), self.matrix_listener_killer.clone(),
client.clone(), client.clone(),
)); ));
@ -204,8 +191,4 @@ impl App<'_> {
pub fn client(&self) -> Option<&Client> { pub fn client(&self) -> Option<&Client> {
self.accounts_manager.client() self.accounts_manager.client()
} }
pub fn channel_tx(&self) -> &mpsc::Sender<Event> {
&self.channel_tx
}
} }

View File

@ -1,5 +1,5 @@
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use cli_log::{warn, info}; use cli_log::{info, warn};
use indexmap::IndexMap; use indexmap::IndexMap;
use matrix_sdk::{ use matrix_sdk::{
room::MessagesOptions, room::MessagesOptions,
@ -62,7 +62,7 @@ impl Room {
let events = self.matrix_room.messages(messages_options).await?; let events = self.matrix_room.messages(messages_options).await?;
self.timeline_end = events.end; self.timeline_end = events.end;
for event in events.chunk.iter() { for event in events.chunk {
self.timeline.insert( self.timeline.insert(
0, 0,
match event.event.deserialize() { match event.event.deserialize() {

29
src/app/transmitter.rs Normal file
View File

@ -0,0 +1,29 @@
use anyhow::{bail, Context, Result};
use tokio::sync::mpsc;
use super::events::event_types::Event;
pub struct Transmitter {
tx: mpsc::Sender<Event>,
rx: mpsc::Receiver<Event>,
}
impl Transmitter {
pub fn new() -> Transmitter {
let (tx, rx) = mpsc::channel(256);
Transmitter { tx, rx }
}
pub fn tx(&self) -> mpsc::Sender<Event> {
self.tx.to_owned()
}
pub async fn recv(&mut self) -> Result<Event> {
match self.rx.recv().await {
Some(event) => Ok(event),
None => bail!("Event channel has no senders"),
}
}
pub async fn send(&mut self, event: Event) -> Result<()> {
self.tx.send(event).await.context("Failed to send event")
}
}