Lev
3 years ago
14 changed files with 628 additions and 430 deletions
@ -1,26 +1 @@
|
||||
use crate::message::{DegMessage, Profile}; |
||||
use ironforce::PublicKey; |
||||
use crate::state::AppScreen; |
||||
|
||||
/// An enum with all possible events for this application
|
||||
#[derive(Clone, Debug)] |
||||
pub enum GuiEvent { |
||||
/// Selection of a chat
|
||||
ChatSelect(usize), |
||||
/// The user changed the value of "new message" field
|
||||
Typed(String), |
||||
/// The user clicked "Send"
|
||||
SendMessage, |
||||
/// A new messaged arrived
|
||||
NewMessageInChat(PublicKey, DegMessage), |
||||
/// A profile response arrived and we should store it
|
||||
SetProfile(PublicKey, Profile), |
||||
/// We should send profile (in response to profile request)
|
||||
WeHaveToSendProfile(PublicKey), |
||||
/// Go to another screen
|
||||
ChangeScreen(AppScreen), |
||||
/// Changed the name in the field on profile screen
|
||||
ChangeName(String), |
||||
/// Nothing happened
|
||||
None, |
||||
} |
||||
pub use degeon_core::{GuiEvent, AppScreen}; |
||||
|
@ -1,48 +1 @@
|
||||
use ironforce::PublicKey; |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// A message in the messenger
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||
pub struct DegMessage { |
||||
pub sender: PublicKey, |
||||
pub timestamp: i64, |
||||
pub content: DegMessageContent, |
||||
} |
||||
|
||||
impl DegMessage { |
||||
/// Create a simple text message
|
||||
pub fn new_text(text: String, my_key: &PublicKey) -> DegMessage { |
||||
Self { |
||||
sender: my_key.clone(), |
||||
timestamp: chrono::Utc::now().timestamp(), |
||||
content: DegMessageContent::Text(text), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// The content of the message
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||
pub enum DegMessageContent { |
||||
Text(String), |
||||
File(Vec<u8>), |
||||
Service, |
||||
} |
||||
|
||||
/// User's profile
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)] |
||||
pub struct Profile { |
||||
pub name: String, |
||||
} |
||||
|
||||
/// A protocol message (that's sent through IF)
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||
pub enum ProtocolMsg { |
||||
/// Requesting profile
|
||||
ProfileRequest, |
||||
/// Responding to the profile request with a profile
|
||||
ProfileResponse(Profile), |
||||
/// A peer discovery
|
||||
Ping, |
||||
/// A message is sent
|
||||
NewMessage(DegMessage), |
||||
} |
||||
pub use degeon_core::{ProtocolMsg, Profile, DegMessage, DegMessageContent}; |
||||
|
@ -0,0 +1,25 @@
|
||||
[package] |
||||
name = "degeon_core" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
||||
[dependencies] |
||||
ironforce = { path = "..", features = ["std"] } |
||||
base64 = "0.13.0" |
||||
serde = { version = "1.0" } |
||||
serde_json = "1.0.72" |
||||
futures = "0.3.18" |
||||
chrono = "0.4.19" |
||||
iced = { version = "0.3.0", features = ["glow"] } |
||||
|
||||
[dependencies.pyo3] |
||||
version = "0.14.0" |
||||
features = ["extension-module"] |
||||
|
||||
[lib] |
||||
name = "degeon_core" |
||||
path = "src/lib.rs" |
||||
crate-type = ["cdylib", "rlib"] |
||||
|
@ -0,0 +1,56 @@
|
||||
use ironforce::{Keys, PublicKey}; |
||||
use crate::message::{DegMessage, DegMessageContent, Profile}; |
||||
use serde::{Serialize, Deserialize}; |
||||
|
||||
|
||||
/// A chat in the messenger
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||
pub struct Chat { |
||||
/// Public key of the other user
|
||||
pub pkey: PublicKey, |
||||
/// Messages in this chat
|
||||
pub messages: Vec<DegMessage>, |
||||
/// Profile of the other user
|
||||
pub profile: Profile, |
||||
/// Scroll position
|
||||
pub scrolled: f32, |
||||
/// Message in the field
|
||||
pub input: String, |
||||
} |
||||
|
||||
impl Chat { |
||||
/// Create a new chat
|
||||
pub fn new(pkey: PublicKey) -> Self { |
||||
Self { |
||||
pkey, |
||||
messages: vec![], |
||||
profile: Profile { name: "".to_string() }, |
||||
scrolled: 0.0, |
||||
input: "".to_string(), |
||||
} |
||||
} |
||||
|
||||
/// Create an example chat
|
||||
pub fn example(i: usize, my_keys: &Keys) -> Chat { |
||||
let pkey = Keys::generate().get_public(); |
||||
Self { |
||||
messages: vec![ |
||||
DegMessage { |
||||
sender: pkey.clone(), |
||||
timestamp: chrono::Utc::now().timestamp(), |
||||
content: DegMessageContent::Text(format!("Example message {}", 2 * i)) |
||||
}, |
||||
DegMessage { |
||||
sender: my_keys.get_public(), |
||||
timestamp: chrono::Utc::now().timestamp(), |
||||
content: DegMessageContent::Text(format!("Example message {}", 2 * i + 1)) |
||||
}, |
||||
|
||||
], |
||||
profile: Profile { name: format!("Example user ({})", i) }, |
||||
scrolled: 0.0, |
||||
pkey, |
||||
input: "".to_string(), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,257 @@
|
||||
use crate::chat::Chat; |
||||
use crate::gui_events::GuiEvent; |
||||
use crate::message::{Profile, ProtocolMsg}; |
||||
use futures::Stream; |
||||
use ironforce::res::IFResult; |
||||
use ironforce::{IronForce, Keys, Message, MessageType, PublicKey}; |
||||
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
|
||||
pub chats: Vec<Chat>, |
||||
/// Profile of this user
|
||||
pub profile: Profile, |
||||
/// Keys of this user
|
||||
pub keys: Keys, |
||||
/// The IF worker
|
||||
pub ironforce: Arc<Mutex<IronForce>>, |
||||
} |
||||
|
||||
/// Data for serialization
|
||||
#[derive(Serialize, Deserialize)] |
||||
pub struct DegeonData { |
||||
pub chats: Vec<Chat>, |
||||
pub profile: Profile, |
||||
pub keys: Keys, |
||||
} |
||||
|
||||
/// Load IF and launch the main loop
|
||||
///
|
||||
/// Returns ironforce and keys
|
||||
fn get_initialized_ironforce() -> (Arc<Mutex<IronForce>>, Keys) { |
||||
let ironforce = IronForce::from_file("".to_string()).unwrap(); |
||||
let keys = ironforce.keys.clone(); |
||||
println!("ID: {}", keys.get_public().get_short_id()); |
||||
let (_thread, ironforce) = ironforce.launch_main_loop(1000); |
||||
(ironforce, keys) |
||||
} |
||||
|
||||
impl Default for Degeon { |
||||
fn default() -> Self { |
||||
let (ironforce, keys) = get_initialized_ironforce(); |
||||
Self { |
||||
chats: vec![], |
||||
profile: Profile::default(), |
||||
keys, |
||||
ironforce, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Degeon { |
||||
/// Get profile for the current user
|
||||
pub fn get_profile(&self) -> Profile { |
||||
self.profile.clone() |
||||
} |
||||
|
||||
/// Find a chat in the list for a given public key
|
||||
pub fn chat_with(&self, pkey: &PublicKey) -> Option<usize> { |
||||
self.chats.iter().position(|chat| &chat.pkey == pkey) |
||||
} |
||||
|
||||
/// Process the incoming message and act accordingly
|
||||
pub fn process_message(&self, msg: ironforce::Message) -> IFResult<Option<GuiEvent>> { |
||||
let deg_msg: ProtocolMsg = |
||||
match serde_json::from_slice(msg.get_decrypted(&self.keys)?.as_slice()) { |
||||
Ok(r) => r, |
||||
Err(_) => return Ok(None), |
||||
}; |
||||
let sender = msg.get_sender(&self.keys).unwrap(); |
||||
println!( |
||||
"check_rec: {:?}, sender==self: {:?}", |
||||
msg.check_recipient(&self.keys), |
||||
sender == self.keys.get_public() |
||||
); |
||||
if !msg.check_recipient(&self.keys) || sender == self.keys.get_public() { |
||||
return Ok(None); |
||||
} |
||||
println!("{:?}", deg_msg); |
||||
Ok(match °_msg { |
||||
ProtocolMsg::NewMessage(deg_msg) => { |
||||
Some(GuiEvent::NewMessageInChat(sender, deg_msg.clone())) |
||||
} |
||||
ProtocolMsg::ProfileRequest | ProtocolMsg::Ping => { |
||||
Some(GuiEvent::WeHaveToSendProfile(sender)) |
||||
} |
||||
ProtocolMsg::ProfileResponse(prof) => Some(GuiEvent::SetProfile(sender, prof.clone())), |
||||
}) |
||||
} |
||||
|
||||
/// Send a multicast message through the network
|
||||
pub fn send_multicast(&self, msg: ProtocolMsg) -> IFResult<()> { |
||||
self.ironforce.lock().unwrap().send_to_all( |
||||
Message::build() |
||||
.message_type(MessageType::Broadcast) |
||||
.content(serde_json::to_vec(&msg)?) |
||||
.sign(&self.keys) |
||||
.build()?, |
||||
) |
||||
} |
||||
|
||||
/// Send a message to a target through the network
|
||||
pub fn send_message(&self, msg: ProtocolMsg, target: &PublicKey) -> IFResult<()> { |
||||
// if self.ironforce.lock().unwrap().get_tunnel(target).is_none() {
|
||||
// println!("Creating a tunnel");
|
||||
// self.ironforce
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .initialize_tunnel_creation(target)?;
|
||||
// let mut counter = 0;
|
||||
// while self.ironforce.lock().unwrap().get_tunnel(target).is_none() {
|
||||
// std::thread::sleep(std::time::Duration::from_millis(350));
|
||||
// counter += 1;
|
||||
// if counter > 100 {
|
||||
// return Err(IFError::TunnelNotFound);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
self.ironforce.lock().unwrap().send_to_all( |
||||
Message::build() |
||||
.message_type(MessageType::Broadcast) |
||||
.content(serde_json::to_vec(&msg)?) |
||||
.recipient(target) |
||||
.sign(&self.keys) |
||||
.build()?, |
||||
) |
||||
} |
||||
|
||||
/// Created an iced command that sends a message to a target
|
||||
pub fn get_send_command( |
||||
&self, |
||||
msg: ProtocolMsg, |
||||
target: &PublicKey, |
||||
) -> iced::Command<GuiEvent> { |
||||
let if_clone = self.ironforce.clone(); |
||||
let _target = target.clone(); |
||||
let keys = self.keys.clone(); |
||||
|
||||
println!("Creating a send command: {:?}", msg); |
||||
iced::Command::perform(async {}, move |_| { |
||||
println!("Sending message: {:?}", msg); |
||||
if_clone |
||||
.lock() |
||||
.unwrap() |
||||
.send_to_all( |
||||
Message::build() |
||||
.message_type(MessageType::Broadcast) |
||||
.content(serde_json::to_vec(&msg).unwrap()) |
||||
// todo:
|
||||
// .recipient(&target)
|
||||
.sign(&keys) |
||||
.build() |
||||
.unwrap(), |
||||
) |
||||
.unwrap(); |
||||
GuiEvent::None |
||||
}) |
||||
} |
||||
} |
||||
|
||||
pub const DEFAULT_FILENAME: &str = ".degeon.json"; |
||||
|
||||
impl Degeon { |
||||
/// Store most of the necessary data to string
|
||||
pub fn serialize_to_string(&self) -> serde_json::Result<String> { |
||||
let data = DegeonData { |
||||
chats: self.chats.clone(), |
||||
profile: self.get_profile(), |
||||
keys: self.keys.clone(), |
||||
}; |
||||
serde_json::to_string(&data) |
||||
} |
||||
|
||||
/// Restore `Degeon` from serialized data
|
||||
pub fn restore_from_string(data: String) -> IFResult<Self> { |
||||
let data_res: serde_json::Result<DegeonData> = serde_json::from_str(data.as_str()); |
||||
let data = match data_res { |
||||
Ok(r) => r, |
||||
Err(_) => return Ok(Self::default()), |
||||
}; |
||||
let (ironforce, _keys) = get_initialized_ironforce(); |
||||
ironforce.lock().unwrap().keys = data.keys.clone(); |
||||
let deg = Degeon { |
||||
chats: data.chats, |
||||
profile: data.profile, |
||||
keys: data.keys, |
||||
ironforce |
||||
}; |
||||
Ok(deg) |
||||
} |
||||
|
||||
/// Save to a file. If no filename is provided, the default is used
|
||||
pub fn save_to_file(&self, filename: String) -> IFResult<()> { |
||||
let data = self.serialize_to_string()?; |
||||
let filename = if filename.is_empty() { |
||||
DEFAULT_FILENAME.to_string() |
||||
} else { |
||||
filename |
||||
}; |
||||
std::fs::write(filename, data)?; |
||||
Ok(()) |
||||
} |
||||
|
||||
/// Restore from a file. If no filename is provided, the default is used
|
||||
pub fn restore_from_file(filename: String) -> IFResult<Self> { |
||||
let filename = if filename.is_empty() { |
||||
DEFAULT_FILENAME.to_string() |
||||
} else { |
||||
filename |
||||
}; |
||||
let content = std::fs::read_to_string(filename).unwrap_or_default(); |
||||
Self::restore_from_string(content) |
||||
} |
||||
} |
||||
|
||||
impl Stream for Degeon { |
||||
type Item = GuiEvent; |
||||
|
||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { |
||||
let timestamp_0 = std::time::Instant::now(); |
||||
let msg_raw = self.ironforce.lock().unwrap().read_message(); |
||||
let msg = msg_raw |
||||
.as_ref() |
||||
.map(|msg| self.process_message(msg.clone()).unwrap()); |
||||
if msg_raw.is_some() { |
||||
let msg_deg: ProtocolMsg = match serde_json::from_slice( |
||||
msg_raw |
||||
.as_ref() |
||||
.unwrap() |
||||
.get_decrypted(&self.keys) |
||||
.unwrap() |
||||
.as_slice(), |
||||
) { |
||||
Ok(r) => r, |
||||
Err(_) => { |
||||
println!("Couldn't deserialize {:?}", msg_raw); |
||||
return Poll::Ready(Some(GuiEvent::None)); |
||||
} |
||||
}; |
||||
println!("{:?} -> {:?}", msg_deg, msg); |
||||
} |
||||
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)), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,52 @@
|
||||
use crate::message::{DegMessage, Profile}; |
||||
use ironforce::PublicKey; |
||||
use serde::{Serialize, Deserialize}; |
||||
|
||||
|
||||
/// The screens (pages) of the app
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] |
||||
pub enum AppScreen { |
||||
/// The one with the chats
|
||||
Main, |
||||
/// Settings and profile
|
||||
ProfileEditor, |
||||
/// The screen that appears if no peers are available and asks for the IP address of at least one peer
|
||||
#[allow(dead_code)] |
||||
PeerInput, |
||||
} |
||||
|
||||
|
||||
impl Default for AppScreen { |
||||
fn default() -> Self { |
||||
AppScreen::Main |
||||
} |
||||
} |
||||
|
||||
/// An enum with all possible events for this application
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||
pub enum GuiEvent { |
||||
/// Selection of a chat
|
||||
ChatSelect(usize), |
||||
/// The user changed the value of "new message" field
|
||||
Typed(String), |
||||
/// The user clicked "Send"
|
||||
SendMessage, |
||||
/// A new messaged arrived
|
||||
NewMessageInChat(PublicKey, DegMessage), |
||||
/// A profile response arrived and we should store it
|
||||
SetProfile(PublicKey, Profile), |
||||
/// We should send profile (in response to profile request)
|
||||
WeHaveToSendProfile(PublicKey), |
||||
/// Go to another screen
|
||||
ChangeScreen(AppScreen), |
||||
/// Changed the name in the field on profile screen
|
||||
ChangeName(String), |
||||
/// Nothing happened
|
||||
None, |
||||
} |
||||
|
||||
impl GuiEvent { |
||||
pub fn get_json(&self) -> String { |
||||
serde_json::to_string(&self).unwrap() |
||||
} |
||||
} |
@ -0,0 +1,25 @@
|
||||
extern crate serde; |
||||
extern crate pyo3; |
||||
mod chat; |
||||
mod degeon_worker; |
||||
mod gui_events; |
||||
mod message; |
||||
|
||||
pub use chat::Chat; |
||||
pub use degeon_worker::{Degeon, DegeonData, DEFAULT_FILENAME}; |
||||
pub use message::{DegMessage, Profile, ProtocolMsg, DegMessageContent}; |
||||
pub use gui_events::{AppScreen, GuiEvent}; |
||||
use pyo3::prelude::*; |
||||
use pyo3::wrap_pyfunction; |
||||
|
||||
#[pyfunction] |
||||
fn new_degeon() -> PyResult<Degeon> { |
||||
Ok(Degeon::default()) |
||||
} |
||||
|
||||
#[pymodule] |
||||
fn degeon_core(_py: Python, m: &PyModule) -> PyResult<()> { |
||||
m.add_class::<Degeon>()?; |
||||
m.add_function(wrap_pyfunction!(new_degeon, m)?)?; |
||||
Ok(()) |
||||
} |
@ -0,0 +1,48 @@
|
||||
use ironforce::PublicKey; |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// A message in the messenger
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||
pub struct DegMessage { |
||||
pub sender: PublicKey, |
||||
pub timestamp: i64, |
||||
pub content: DegMessageContent, |
||||
} |
||||
|
||||
impl DegMessage { |
||||
/// Create a simple text message
|
||||
pub fn new_text(text: String, my_key: &PublicKey) -> DegMessage { |
||||
Self { |
||||
sender: my_key.clone(), |
||||
timestamp: chrono::Utc::now().timestamp(), |
||||
content: DegMessageContent::Text(text), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// The content of the message
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||
pub enum DegMessageContent { |
||||
Text(String), |
||||
File(Vec<u8>), |
||||
Service, |
||||
} |
||||
|
||||
/// User's profile
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)] |
||||
pub struct Profile { |
||||
pub name: String, |
||||
} |
||||
|
||||
/// A protocol message (that's sent through IF)
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||
pub enum ProtocolMsg { |
||||
/// Requesting profile
|
||||
ProfileRequest, |
||||
/// Responding to the profile request with a profile
|
||||
ProfileResponse(Profile), |
||||
/// A peer discovery
|
||||
Ping, |
||||
/// A message is sent
|
||||
NewMessage(DegMessage), |
||||
} |
Loading…
Reference in new issue