From 05785e078cc3a426b5489a63bfe2edf5f0ed56f7 Mon Sep 17 00:00:00 2001 From: ennucore Date: Sat, 18 Dec 2021 17:58:59 +0300 Subject: [PATCH] Add handling loop iteration for python --- degeon/src/state.rs | 34 ++-------- degeon_core/src/chat.rs | 4 ++ degeon_core/src/degeon_worker.rs | 113 ++++++++++++++++++++++++++----- degeon_core/src/lib.rs | 6 +- degeon_core/src/message.rs | 69 +++++++++++++++++++ src/ironforce.rs | 2 +- 6 files changed, 181 insertions(+), 47 deletions(-) diff --git a/degeon/src/state.rs b/degeon/src/state.rs index 4b5c32d..087d7b6 100644 --- a/degeon/src/state.rs +++ b/degeon/src/state.rs @@ -198,14 +198,6 @@ impl Application for DegeonApp { name_input_state: Default::default(), profile_done_button_state: Default::default(), }; - let data_clone = st.data.clone(); - std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_secs(10)); - loop { - data_clone.send_multicast(ProtocolMsg::Ping).unwrap(); - std::thread::sleep(std::time::Duration::from_secs(120)); - } - }); (st, iced::Command::none()) } @@ -214,6 +206,7 @@ impl Application for DegeonApp { } fn update(&mut self, message: GuiEvent, _: &mut iced::Clipboard) -> iced::Command { + self.data.process_event(&message, false).unwrap(); match message { GuiEvent::ChatSelect(i) => self.selected_chat = i, GuiEvent::Typed(st) => self.data.chats[self.selected_chat].input = st, @@ -238,22 +231,14 @@ impl Application for DegeonApp { }); self.data.save_to_file("".to_string()).unwrap(); } - GuiEvent::NewMessageInChat(pkey, msg) => { - if self.data.chat_with(&pkey).is_none() { - self.data.chats.push(Chat::new(pkey.clone())) - } - let ind = self.data.chat_with(&pkey).unwrap(); - self.data.chats[ind].messages.push(msg); - self.data.save_to_file("".to_string()).unwrap(); - } - GuiEvent::SetProfile(pkey, name) => { - if self.data.chat_with(&pkey).is_none() { - self.data.chats.push(Chat::new(pkey.clone())) - } - let ind = self.data.chat_with(&pkey).unwrap(); - self.data.chats[ind].profile = name; + GuiEvent::ChangeScreen(sc) => { + self.screen = sc; self.data.save_to_file("".to_string()).unwrap(); } + GuiEvent::ChangeName(name) => self.data.profile.name = name, + // The following events are already handled in Degeon::process_event + GuiEvent::NewMessageInChat(_pkey, _msg) => {} + GuiEvent::SetProfile(_pkey, _profile) => { } GuiEvent::None => {} GuiEvent::WeHaveToSendProfile(target) => { println!("WHTSP"); @@ -262,11 +247,6 @@ impl Application for DegeonApp { &target, ); } - GuiEvent::ChangeScreen(sc) => { - self.screen = sc; - self.data.save_to_file("".to_string()).unwrap(); - } - GuiEvent::ChangeName(name) => self.data.profile.name = name, } iced::Command::none() } diff --git a/degeon_core/src/chat.rs b/degeon_core/src/chat.rs index d6f5f1f..6673ed5 100644 --- a/degeon_core/src/chat.rs +++ b/degeon_core/src/chat.rs @@ -1,16 +1,20 @@ use ironforce::{Keys, PublicKey}; use crate::message::{DegMessage, DegMessageContent, Profile}; use serde::{Serialize, Deserialize}; +use pyo3::prelude::*; /// A chat in the messenger #[derive(Clone, Debug, Serialize, Deserialize)] +#[pyclass] pub struct Chat { /// Public key of the other user pub pkey: PublicKey, /// Messages in this chat + #[pyo3(get)] pub messages: Vec, /// Profile of the other user + #[pyo3(get, set)] pub profile: Profile, /// Scroll position pub scrolled: f32, diff --git a/degeon_core/src/degeon_worker.rs b/degeon_core/src/degeon_worker.rs index 53f7286..f1267e0 100644 --- a/degeon_core/src/degeon_worker.rs +++ b/degeon_core/src/degeon_worker.rs @@ -1,22 +1,25 @@ use crate::chat::Chat; use crate::gui_events::GuiEvent; use crate::message::{Profile, ProtocolMsg}; +use crate::DegMessage; use futures::Stream; use ironforce::res::IFResult; use ironforce::{IronForce, Keys, Message, MessageType, PublicKey}; +use pyo3::prelude::*; +use serde::{Deserialize, Serialize}; use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; -use pyo3::prelude::*; -use serde::{Serialize, Deserialize}; /// The container for logic, data, IF and protocol interactions #[derive(Clone)] #[pyclass] pub struct Degeon { /// The list of all chats for this instance + #[pyo3(get, set)] pub chats: Vec, /// Profile of this user + #[pyo3(get, set)] pub profile: Profile, /// Keys of this user pub keys: Keys, @@ -46,12 +49,21 @@ fn get_initialized_ironforce() -> (Arc>, Keys) { impl Default for Degeon { fn default() -> Self { let (ironforce, keys) = get_initialized_ironforce(); - Self { + let st = Self { chats: vec![], profile: Profile::default(), keys, ironforce, - } + }; + let self_clone = st.clone(); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_secs(2)); + loop { + self_clone.send_multicast(ProtocolMsg::Ping).unwrap(); + std::thread::sleep(std::time::Duration::from_secs(120)); + } + }); + st } } @@ -122,6 +134,7 @@ impl Degeon { // } // } // } + println!("Sending: {:?}", msg); self.ironforce.lock().unwrap().send_to_all( Message::build() .message_type(MessageType::Broadcast) @@ -162,6 +175,66 @@ impl Degeon { GuiEvent::None }) } + + /// Process a GuiEvent if it's connected to the worker (like sending a message) + pub fn process_event(&mut self, event: &GuiEvent, perform_sending: bool) -> IFResult<()> { + match event { + GuiEvent::NewMessageInChat(pkey, msg) => { + if self.chat_with(&pkey).is_none() { + self.chats.push(Chat::new(pkey.clone())) + } + let ind = self.chat_with(&pkey).unwrap(); + self.chats[ind].messages.push(msg.clone()); + self.save_to_file("".to_string())?; + } + GuiEvent::SetProfile(pkey, profile) => { + if self.chat_with(&pkey).is_none() { + self.chats.push(Chat::new(pkey.clone())) + } + let ind = self.chat_with(&pkey).unwrap(); + self.chats[ind].profile = profile.clone(); + self.save_to_file("".to_string())?; + } + GuiEvent::WeHaveToSendProfile(target) if perform_sending => { + let target = target.clone(); + let self_cloned = self.clone(); + std::thread::spawn(move || { + self_cloned.send_message( + ProtocolMsg::ProfileResponse(self_cloned.get_profile()), + &target, + ) + }); + } + _ => {} + } + Ok(()) + } +} + +#[pymethods] +impl Degeon { + /// Create a new text message and send it + pub fn send_text_message(&self, text: String, chat_i: usize) -> PyResult<()> { + self.send_message( + ProtocolMsg::NewMessage(DegMessage::new_text(text, &self.keys.get_public())), + &self.chats[chat_i].pkey, + ) + .map_err(|e| { + pyo3::exceptions::PyTypeError::new_err(format!("There was an error in Rust: {:?}", e)) + }) + } + + /// Handle one message + pub fn handling_loop_iteration(&mut self) { + let event = self.read_message_and_create_event(); + if let Some(event) = event { + self.process_event(&event, true).unwrap_or_else(|e| println!("Error: {:?}", e)); + } + } + + pub fn message_queue_len(&self) -> usize { + self.ironforce.lock().unwrap().messages.len() + } } pub const DEFAULT_FILENAME: &str = ".degeon.json"; @@ -190,7 +263,7 @@ impl Degeon { chats: data.chats, profile: data.profile, keys: data.keys, - ironforce + ironforce, }; Ok(deg) } @@ -219,11 +292,8 @@ impl Degeon { } } -impl Stream for Degeon { - type Item = GuiEvent; - - fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - let timestamp_0 = std::time::Instant::now(); +impl Degeon { + pub fn read_message_and_create_event(&self) -> Option { let msg_raw = self.ironforce.lock().unwrap().read_message(); let msg = msg_raw .as_ref() @@ -240,18 +310,27 @@ impl Stream for Degeon { Ok(r) => r, Err(_) => { println!("Couldn't deserialize {:?}", msg_raw); - return Poll::Ready(Some(GuiEvent::None)); + return Some(GuiEvent::None); } }; println!("{:?} -> {:?}", msg_deg, msg); } + match msg { + Some(Some(event)) => Some(event), + _ => None + } + } +} + +impl Stream for Degeon { + type Item = GuiEvent; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + let timestamp_0 = std::time::Instant::now(); + let msg = self.read_message_and_create_event(); if timestamp_0.elapsed() < std::time::Duration::from_millis(5) { std::thread::sleep(std::time::Duration::from_millis(5)); } - match msg { - None => Poll::Ready(Some(GuiEvent::None)), - Some(None) => Poll::Ready(Some(GuiEvent::None)), - Some(Some(msg)) => Poll::Ready(Some(msg)), - } + Poll::Ready(Some(msg.unwrap_or(GuiEvent::None))) } -} \ No newline at end of file +} diff --git a/degeon_core/src/lib.rs b/degeon_core/src/lib.rs index 3d0e046..1a4848e 100644 --- a/degeon_core/src/lib.rs +++ b/degeon_core/src/lib.rs @@ -7,19 +7,21 @@ mod message; pub use chat::Chat; pub use degeon_worker::{Degeon, DegeonData, DEFAULT_FILENAME}; -pub use message::{DegMessage, Profile, ProtocolMsg, DegMessageContent}; +pub use message::{DegMessage, Profile, ProtocolMsg, DegMessageContent, DegMessageContentPy}; pub use gui_events::{AppScreen, GuiEvent}; use pyo3::prelude::*; use pyo3::wrap_pyfunction; #[pyfunction] fn new_degeon() -> PyResult { - Ok(Degeon::default()) + Ok(Degeon::restore_from_file("".to_string()).unwrap()) } #[pymodule] fn degeon_core(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_function(wrap_pyfunction!(new_degeon, m)?)?; Ok(()) } diff --git a/degeon_core/src/message.rs b/degeon_core/src/message.rs index a536bc6..da17387 100644 --- a/degeon_core/src/message.rs +++ b/degeon_core/src/message.rs @@ -1,8 +1,10 @@ use ironforce::PublicKey; +use pyo3::prelude::*; use serde::{Deserialize, Serialize}; /// A message in the messenger #[derive(Clone, Debug, Serialize, Deserialize)] +#[pyclass] pub struct DegMessage { pub sender: PublicKey, pub timestamp: i64, @@ -20,6 +22,13 @@ impl DegMessage { } } +#[pymethods] +impl DegMessage { + pub fn get_content_py(&self) -> DegMessageContentPy { + self.content.get_py() + } +} + /// The content of the message #[derive(Clone, Debug, Serialize, Deserialize)] pub enum DegMessageContent { @@ -30,7 +39,9 @@ pub enum DegMessageContent { /// User's profile #[derive(Clone, Debug, Serialize, Deserialize, Default)] +#[pyclass] pub struct Profile { + #[pyo3(get, set)] pub name: String, } @@ -46,3 +57,61 @@ pub enum ProtocolMsg { /// A message is sent NewMessage(DegMessage), } + +/// DegMessageContent, but a struct for python +#[derive(Clone, Debug, Serialize, Deserialize)] +#[pyclass] +pub struct DegMessageContentPy { + /// If `DegMessageContent` is `Text(st)`, then this is `Some(st)` + #[pyo3(get, set)] + pub text: Option, + /// If `DegMessageContent` is `File(data)`, then this is `Some(data)` + #[pyo3(get, set)] + pub file: Option>, +} + +impl DegMessageContent { + /// Convert from `DegMessageContent` to the corresponding `DegMessageContentPy` + pub fn get_py(&self) -> DegMessageContentPy { + match self { + DegMessageContent::Text(text) => DegMessageContentPy { + text: Some(text.clone()), + file: None, + }, + DegMessageContent::File(data) => DegMessageContentPy { + text: None, + file: Some(data.clone()), + }, + DegMessageContent::Service => DegMessageContentPy { + text: None, + file: None, + }, + } + } +} + +impl DegMessageContentPy { + /// Convert from `DegMessageContentPy` to the corresponding `DegMessageContent` + pub fn to_enum(&self) -> DegMessageContent { + match self { + DegMessageContentPy { + text: Some(text), .. + } => DegMessageContent::Text(text.clone()), + DegMessageContentPy { + file: Some(data), .. + } => DegMessageContent::File(data.clone()), + _ => DegMessageContent::Service, + } + } +} + +#[pymethods] +impl DegMessageContentPy { + #[staticmethod] + pub fn new_text(text: String) -> Self { + Self { + text: Some(text), + file: None, + } + } +} diff --git a/src/ironforce.rs b/src/ironforce.rs index 466ae39..74d3a04 100644 --- a/src/ironforce.rs +++ b/src/ironforce.rs @@ -30,7 +30,7 @@ pub struct IronForce { /// and some kind of decentralized storage additional_modules: Vec<()>, /// Non-service messages to give outside - messages: Vec, + pub messages: Vec, /// Tunnels that has not been confirmed yet (no backward spread) /// /// `[(Tunnel, Optional target node, local peer ids)]`