Browse Source

Documentation

master
Lev 3 years ago
parent
commit
3f49b7a551
  1. 54
      README.md
  2. 25
      degeon/src/chat.rs
  3. 9
      degeon/src/degeon_worker.rs
  4. 1
      degeon/src/gui_events.rs
  5. 2
      degeon/src/main.rs
  6. 1
      degeon/src/message.rs
  7. BIN
      degeon/src/profile_logo.png
  8. 8
      degeon/src/state.rs
  9. 2
      degeon/src/styles.rs
  10. 65
      degeon_core/src/chat.rs
  11. 2
      degeon_core/src/degeon_worker.rs
  12. 17
      degeon_core/src/lib.rs
  13. 6
      degeon_core/src/message.rs
  14. 6
      degeon_py/degeon.py
  15. BIN
      degeon_py/degeon_core.so
  16. 1
      degeon_py/input_field.py
  17. BIN
      images/degeon_logo.png
  18. BIN
      images/dependency_graph.png
  19. 207
      images/dependency_graph.svg

54
README.md

@ -2,7 +2,14 @@
IronForce is a peer-to-peer decentralized network, Degeon is a messenger built on it.
![](images/logo.png)
In this repository there is three Cargo crates and one python project:
- `ironforce` is the decentralized network
- `degeon_core` contains messenger's protocol
- `degeon` is the messenger app in Rust + Iced
- `degeon_py` is a version of the messenger app in Python + Pygame
![](images/dependency_graph.png)
# Ironforce
@ -20,6 +27,49 @@ The Ironforce network:
![](images/scheme.png)
### Running IronForce
The `IF` network has a worker daemon that can be used to read the broadcast messages and write them.
Also, you can us `IF` as a Rust library (as it's done in Degeon).
To get started:
1. Clone this repository: `git clone ssh://git@gitlab.ennucore.com:10022/ironforce/ironforce.git`
2. Install Rust and Cargo. The best way to do this is to use RustUp:
```shell
sudo pacman -S rustup # this is for Arch Linux, on other distros use the corresponding package manager
rustup default nightly # this will install rust & cargo (nightly toolchain)
```
3. Run the worker:
```shell
cd ironforce/ironforce
cargo build --features std # This will build ironforce network
# To run the worker, use this:
cargo run --bin worker --features std
```
# Degeon
Degeon is just a messenger built on IronForce to show its abilities.
Degeon is a messenger built on IronForce to show its abilities.
Its core is in the crate `degeon_core`. The crate can be used as a Python module or in Rust.
To build it, install Cargo and Rust, then do this:
```shell
cd ironforce/degeon_core
cargo build
```
You will find the `libdegeon_core.so` file that can be used in Python in the `target/debug` directory.
It should be renamed to `degeon_core.so`.
In Windows Rust should generate a `.dll` file that you will be able to use likewise.
There are two GUI options for this interface:
- A Rust + Iced client (preferred)
- A Pygame client
To run the Rust client, `cd` into the `degeon` folder of this repository, then use cargo:
`cargo run`.
Note: this application requires Vulkan to be installed.
For me on Arch Linux and Intel GPU the installation command was `sudo pacman -S vulkan-intel`.
On most systems it should be pre-installed.

25
degeon/src/chat.rs

@ -5,9 +5,10 @@ pub use degeon_core::Chat;
use iced::{Align, Button, Column, Element, Length, Row, Text, TextInput};
use iced_native::button;
/// A chat that can be rendered in iced
pub trait RenderableChat {
/// Render header of the chat
fn header<'a>(name: String) -> Element<'a, GuiEvent>;
fn header<'a>(name: String, bio: String) -> Element<'a, GuiEvent>;
/// Render the sending field
fn send_field<'a>(
@ -35,8 +36,14 @@ pub trait RenderableChat {
impl RenderableChat for Chat {
/// Render header of the chat
fn header<'a>(name: String) -> Element<'a, GuiEvent> {
iced::container::Container::new(Text::new(name.as_str()).color(iced::Color::WHITE))
///
/// The header is a rectangle (container) with name and bio
fn header<'a>(name: String, bio: String) -> Element<'a, GuiEvent> {
iced::container::Container::new(Column::with_children(
vec![
Text::new(name.as_str()).size(20).color(iced::Color::WHITE).into(),
Text::new(bio.as_str()).size(13).color(iced::Color::from_rgb(0.6, 0.6, 0.6)).into(),
]))
.style(style::Container::Primary)
.width(Length::Fill)
.height(Length::Units(50))
@ -45,6 +52,9 @@ impl RenderableChat for Chat {
}
/// Render the sending field
///
/// This field consists of a text input and a send button.
/// When the button is clicked, this element creates an event
fn send_field<'a>(
input: String,
text_input_state: &'a mut iced::text_input::State,
@ -53,6 +63,7 @@ impl RenderableChat for Chat {
Row::new()
.width(Length::Fill)
.padding(15)
// Text field
.push(
TextInput::new(text_input_state, "Message", input.as_str(), |st| {
GuiEvent::Typed(st)
@ -60,6 +71,7 @@ impl RenderableChat for Chat {
.padding(8)
.width(Length::Fill),
)
// Send button
.push(
Button::new(send_button_state, Text::new("Send"))
.on_press(GuiEvent::SendMessage)
@ -92,6 +104,8 @@ impl RenderableChat for Chat {
}
/// Render the chat view
///
/// Chat view consists of a header, the messages and the input field
fn view<'a>(
&'a self,
text_input_state: &'a mut iced::text_input::State,
@ -108,7 +122,9 @@ impl RenderableChat for Chat {
.align_items(Align::End)
.height(Length::Fill)
.width(Length::FillPortion(4))
.push(Self::header(self.profile.name.clone()))
// Header
.push(Self::header(self.profile.name.clone(), self.profile.bio.clone()))
// Messages (scrollable)
.push(
iced::Container::new(
iced::Scrollable::new(scroll_state)
@ -125,6 +141,7 @@ impl RenderableChat for Chat {
.height(Length::FillPortion(9)),
)
.spacing(10)
// New message field
.push(Self::send_field(
self.input.to_string(),
text_input_state,

9
degeon/src/degeon_worker.rs

@ -1,11 +1,13 @@
use crate::gui_events::GuiEvent;
pub use degeon_core::{Degeon, DegeonData};
/// A wrapper around `degeon_core`'s `Degeon` that can create a subscription for events
pub(crate) struct DegeonContainer {
inner: Degeon,
}
impl DegeonContainer {
/// Create a new container from inner data
pub fn new(data: &Degeon) -> Self {
Self { inner: data.clone() }
}
@ -22,13 +24,6 @@ where
std::any::TypeId::of::<Self>().hash(state);
self.inner.ironforce.lock().unwrap().hash(state);
// std::time::SystemTime::now().hash(state);
// std::time::UNIX_EPOCH
// .elapsed()
// .unwrap()
// .as_secs()
// .hash(state);
}
fn stream(

1
degeon/src/gui_events.rs

@ -1 +1,2 @@
// Just reusing events from `degeon_core`
pub use degeon_core::{GuiEvent, AppScreen};

2
degeon/src/main.rs

@ -10,7 +10,9 @@ use iced::Application;
use crate::state::DegeonApp;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Change default font
let mut settings = iced::Settings::default();
settings.default_font = Some(include_bytes!("CRC35.otf"));
// Create and run the app
Ok(DegeonApp::run(settings)?)
}

1
degeon/src/message.rs

@ -1 +1,2 @@
/// Messages here are just from `degeon_core`
pub use degeon_core::{ProtocolMsg, Profile, DegMessage, DegMessageContent};

BIN
degeon/src/profile_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

8
degeon/src/state.rs

@ -13,6 +13,9 @@ use iced::{
use ironforce::PublicKey;
/// Render a message into an iced `Element`
///
/// A message is a rounded rectangle with text.
/// The background color of the rectangle depends on the sender of the message.
pub fn view_message(msg: &DegMessage, pkey: PublicKey) -> Option<Element<GuiEvent>> {
let is_from_me = pkey != msg.sender;
match &msg.content {
@ -128,6 +131,7 @@ impl DegeonApp {
}
impl DegeonApp {
/// Render the main screen with chat list and active chat on it
fn main_view(&mut self) -> Element<GuiEvent> {
let Self {
data: Degeon { chats, .. },
@ -158,6 +162,7 @@ impl DegeonApp {
.into()
}
/// Render the screen with profile settings
fn profile_editor(&mut self) -> Element<GuiEvent> {
let Self {
data:
@ -232,10 +237,11 @@ impl Application for DegeonApp {
type Message = GuiEvent;
type Flags = ();
// Create a new application instance
fn new(_: ()) -> (Self, iced::Command<GuiEvent>) {
let mut data = Degeon::restore_from_file("".to_string()).unwrap();
if data.chats.is_empty() {
data.chats = vec![Chat::example(1, &data.keys), Chat::example(2, &data.keys)];
data.chats = vec![Chat::example(0, &data.keys), Chat::example(1, &data.keys)];
}
data.send_multicast(ProtocolMsg::Ping).unwrap();
let mut scroll: iced::scrollable::State = Default::default();

2
degeon/src/styles.rs

@ -2,6 +2,7 @@ pub mod style {
use iced::container::Style;
use iced::{button, Background, Color, Vector};
/// The styles for buttons used in the app
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Button {
Primary,
@ -32,6 +33,7 @@ pub mod style {
}
}
/// The styles for containers used in the app
#[derive(Copy, Clone, PartialEq)]
pub enum Container {
Primary,

65
degeon_core/src/chat.rs

@ -1,10 +1,11 @@
use ironforce::{Keys, PublicKey};
use crate::message::{DegMessage, DegMessageContent, Profile};
use serde::{Serialize, Deserialize};
use ironforce::{Keys, PublicKey};
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
/// A chat in the messenger
///
/// Contains user's profile and all the messages
#[derive(Clone, Debug, Serialize, Deserialize)]
#[pyclass]
pub struct Chat {
@ -28,7 +29,10 @@ impl Chat {
Self {
pkey,
messages: vec![],
profile: Profile { name: "".to_string(), bio: "".to_string() },
profile: Profile {
name: "".to_string(),
bio: "".to_string(),
},
scrolled: 0.0,
input: "".to_string(),
}
@ -36,22 +40,47 @@ impl Chat {
/// Create an example chat
pub fn example(i: usize, my_keys: &Keys) -> Chat {
// A dummy key
let pkey = Keys::generate().get_public();
Self {
messages: vec![
DegMessage {
let mut messages = vec![];
if i == 0 {
for st in vec![
"Welcome to Degeon!",
"It's a messenger app built on the IronForce decentralized network",
"Thanks to IronForce decentralized network, it's completely secure and anonymous",
"Your messages are encrypted and signed by you",
] {
messages.push(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), bio: "".to_string() },
timestamp: 0,
content: DegMessageContent::Text(st.to_string()),
});
}
} else {
messages.push(DegMessage {
sender: pkey.clone(),
timestamp: 0,
content: DegMessageContent::Text(
"You can save information by sending messages in this chat:".to_string(),
),
});
messages.push(DegMessage {
sender: my_keys.get_public(),
timestamp: 0,
content: DegMessageContent::Text("Example saved message".to_string()),
});
}
Self {
messages,
profile: Profile {
name: (if i == 0 {
"Degeon Info"
} else {
"Saved messages"
})
.to_string(),
bio: "".to_string(),
},
scrolled: 0.0,
pkey,
input: "".to_string(),

2
degeon_core/src/degeon_worker.rs

@ -50,7 +50,7 @@ impl Default for Degeon {
fn default() -> Self {
let (ironforce, keys) = get_initialized_ironforce();
let st = Self {
chats: vec![],
chats: vec![Chat::example(0, &keys), Chat::example(1, &keys)],
profile: Profile::default(),
keys,
ironforce,

17
degeon_core/src/lib.rs

@ -17,11 +17,28 @@ fn new_degeon() -> PyResult<Degeon> {
Ok(Degeon::restore_from_file("".to_string()).unwrap())
}
/// Check if there is stored Degeon profile here
#[pyfunction]
fn is_data_available() -> bool {
std::path::Path::new(DEFAULT_FILENAME).exists()
}
/// Create a new profile with name
#[pyfunction]
fn new_degeon_with_name(name: String) -> PyResult<Degeon> {
let mut deg = Degeon::default();
deg.profile.name = name;
Ok(deg)
}
/// Define structs and functions that are exported into python
#[pymodule]
fn degeon_core(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Degeon>()?;
m.add_class::<DegMessageContentPy>()?;
m.add_class::<DegMessage>()?;
m.add_function(wrap_pyfunction!(new_degeon, m)?)?;
m.add_function(wrap_pyfunction!(is_data_available, m)?)?;
m.add_function(wrap_pyfunction!(new_degeon_with_name, m)?)?;
Ok(())
}

6
degeon_core/src/message.rs

@ -6,8 +6,11 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[pyclass]
pub struct DegMessage {
/// Sender's public key
pub sender: PublicKey,
/// UNIX epoch time at which this message was created
pub timestamp: i64,
/// Message content
pub content: DegMessageContent,
}
@ -32,6 +35,7 @@ impl DegMessage {
/// The content of the message
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DegMessageContent {
/// A text message
Text(String),
File(Vec<u8>),
Service,
@ -41,8 +45,10 @@ pub enum DegMessageContent {
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[pyclass]
pub struct Profile {
/// User's name
#[pyo3(get, set)]
pub name: String,
/// User's description
#[pyo3(get, set)]
pub bio: String,
}

6
degeon_py/degeon.py

@ -16,7 +16,11 @@ class Degeon:
"""
The main class with everything connected to the app: the data, the
Attributes
Attributes:
:param core (dc.Degeon): the rust worker
:param chat_selector (ChatSelector): chat list widget
:param active_chat (typing.Optional[ActiveChat]): current chat widget
:param fps (int): FPS rate
"""
core: 'dc.Degeon'
chat_selector: ChatSelector

BIN
degeon_py/degeon_core.so

Binary file not shown.

1
degeon_py/input_field.py

@ -69,7 +69,6 @@ class TextField:
elif event.unicode:
self.value = self.value[:self.cursor_position] + event.unicode + self.value[self.cursor_position:]
self.cursor_position += 1
# print(self.is_focused, event.type, getattr(event, 'key', None), getattr(event, 'unicode', None), self.value)
def collect(self) -> str:
"""

BIN
images/degeon_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

BIN
images/dependency_graph.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 KiB

207
images/dependency_graph.svg

@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="250mm"
height="240mm"
viewBox="0 0 250 240"
version="1.1"
id="svg9118"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
sodipodi:docname="dependency_graph.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview9120"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.64765011"
inkscape:cx="217.71015"
inkscape:cy="561.25984"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
height="250mm" />
<defs
id="defs9115">
<marker
style="overflow:visible"
id="Arrow1Lend"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Lend"
inkscape:isstock="true">
<path
transform="matrix(-0.8,0,0,-0.8,-10,0)"
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path13831" />
</marker>
<marker
style="overflow:visible"
id="Arrow1Lstart"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Lstart"
inkscape:isstock="true">
<path
transform="matrix(0.8,0,0,0.8,10,0)"
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path13828" />
</marker>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient9344"
id="linearGradient9338"
x1="83.937424"
y1="159.45372"
x2="145.70277"
y2="112.71262"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.49339231,0,0,0.49339231,-105.19621,-43.347381)" />
<linearGradient
inkscape:collect="always"
id="linearGradient9344">
<stop
style="stop-color:#ab4251;stop-opacity:1"
offset="0"
id="stop9340" />
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="1"
id="stop9342" />
</linearGradient>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:none;stroke:#282e46;stroke-width:2"
id="rect9201"
width="140.00172"
height="47.340633"
x="61.117081"
y="19.290443"
ry="3.7234547" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:16.0891px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.402227"
x="101.82484"
y="48.617577"
id="text10363"><tspan
sodipodi:role="line"
id="tspan10361"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.402227"
x="101.82484"
y="48.617577">IronForce</tspan></text>
<rect
style="fill:none;stroke:#282e46;stroke-width:2"
id="rect12337"
width="140.00172"
height="47.340633"
x="60.334785"
y="91.636803"
ry="3.7234547" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:16.0891px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.402227"
x="78.434021"
y="120.14689"
id="text12341"><tspan
sodipodi:role="line"
id="tspan12339"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.402227"
x="78.434021"
y="120.14689">Degeon Core</tspan></text>
<rect
style="fill:none;stroke:#282e46;stroke-width:1.54988"
id="rect12445"
width="108.49297"
height="36.686161"
x="7.193059"
y="174.48308"
ry="2.8854549" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.87778px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.311702"
x="21.218885"
y="196.57671"
id="text12449"><tspan
sodipodi:role="line"
id="tspan12447"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87778px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.311702"
x="21.218885"
y="196.57671">Rust Interface</tspan></text>
<rect
style="fill:none;stroke:#282e46;stroke-width:1.54988"
id="rect13683"
width="108.49297"
height="36.686161"
x="135.25137"
y="174.48308"
ry="2.8854549" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.87778px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.311702"
x="142.9272"
y="196.57671"
id="text13687"><tspan
sodipodi:role="line"
id="tspan13685"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87778px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.311702"
x="142.9272"
y="196.57671">Python Interface</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:1.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Lend)"
d="M 59.025518,171.39275 128.53989,142.25592"
id="path13824" />
<path
style="fill:none;stroke:#000000;stroke-width:1.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Lend)"
d="M 185.9168,170.63863 128.53989,142.25592"
id="path13826" />
<path
style="fill:none;stroke:#000000;stroke-width:1.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Lend)"
d="M 131.85776,89.509589 V 68.933222"
id="path14217" />
<g
id="g44157"
transform="matrix(0.50052676,0,0,0.50052676,110.21806,30.396512)"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49">
<circle
style="fill:#282e46;fill-opacity:1;stroke-width:0.130543"
id="path3611"
cx="-50.126827"
cy="23.901407"
r="26.844465" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:33.1511px;line-height:1.25;font-family:sans-serif;fill:url(#linearGradient9338);fill-opacity:1;stroke:none;stroke-width:0.207194"
x="-65.295166"
y="35.411083"
id="text4421"><tspan
sodipodi:role="line"
id="tspan4419"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:33.1511px;font-family:'Intro Rust G';-inkscape-font-specification:'Intro Rust G';fill:url(#linearGradient9338);fill-opacity:1;stroke-width:0.207194"
x="-65.295166"
y="35.411083">IF</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

Loading…
Cancel
Save