use crossbeam_channel::Sender;
use log::{debug, error};
use rand::Rng;
use koibumi_common_sync::boxes::Boxes;
use koibumi_core::{
content, crypto, encoding,
identity::{Private as PrivateIdentity, Public as PublicIdentity},
io::WriteTo,
object,
time::Time,
};
use koibumi_node_sync::Command as NodeCommand;
use conrod_core::{color, text, widget, Colorable, Labelable, Positionable, Sizeable, Widget};
use crate::{config::Config as GuiConfig, gui, ids::Ids, log::Logger};
#[derive(Clone, Debug, Default)]
struct MessageSubTab {
subject_value: String,
body_value: String,
}
#[derive(Clone, Debug, Default)]
struct BroadcastSubTab {
subject_value: String,
body_value: String,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Tab {
sub_tabs: SubTabs,
sub_tab: SubTab,
message_sub_tab: MessageSubTab,
broadcast_sub_tab: BroadcastSubTab,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SubTab {
Message,
Broadcast,
}
impl Default for SubTab {
fn default() -> Self {
Self::Message
}
}
#[derive(Clone, Debug, Default)]
struct SubTabs {
selected: SubTab,
}
impl SubTabs {
pub(crate) fn selected(&self) -> SubTab {
self.selected
}
pub(crate) fn set_widgets(
&mut self,
ui: &mut conrod_core::UiCell,
ids: &mut Ids,
config: &GuiConfig,
) {
let text_size = config.text_size();
widget::Canvas::new()
.flow_down(&[
(
ids.send_tabs_header,
widget::Canvas::new()
.length(text_size as f64 + 8.0 + 2.0 + 2.0)
.pad_bottom(2.0),
),
(ids.send_tabs_body, widget::Canvas::new()),
(
ids.send_buttons_canvas,
widget::Canvas::new()
.length(text_size as f64 + 8.0 + 2.0 + 2.0)
.color(color::DARK_CHARCOAL)
.pad_bottom(2.0),
),
])
.top_left_of(ids.tabs_body)
.wh_of(ids.tabs_body)
.set(ids.send, ui);
let selected = self.selected;
let button = |ui: &mut conrod_core::UiCell, label, tab| {
let mut width = 64.0;
for font_id in ui.fonts.ids() {
width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
}
let c = if selected == tab {
(color::LIGHT_BLUE, color::WHITE)
} else {
(color::DARK_BLUE, color::GRAY)
};
widget::Button::new()
.w_h(width + 8.0, text_size as f64 + 8.0)
.label_font_size(text_size as u32)
.label(label)
.color(c.0)
.label_color(c.1)
};
for _click in button(ui, "Send ordinary Message", SubTab::Message)
.top_left_with_margins_on(ids.send_tabs_header, 2.0, 2.0)
.set(ids.send_tabs_button_message, ui)
{
self.selected = SubTab::Message;
}
for _click in button(ui, "Send Message to your Subscribers", SubTab::Broadcast)
.right_from(ids.send_tabs_button_message, 2.0)
.set(ids.send_tabs_button_broadcast, ui)
{
self.selected = SubTab::Broadcast;
}
}
}
const RANDOM_TTL_SIZE: u64 = 600;
fn send_msg(
command_sender: &mut Sender<NodeCommand>,
from_identity: &PrivateIdentity,
to_identity: &PublicIdentity,
subject: &str,
body: &str,
logger: &mut Logger,
) {
debug!("Start send_msg");
let simple = encoding::Simple::new(subject.as_bytes().to_vec(), body.as_bytes().to_vec());
if let Err(err) = simple {
error!("{}", err);
return;
}
let simple = simple.unwrap();
let mut simple_bytes = Vec::new();
if let Err(err) = simple.write_to(&mut simple_bytes) {
error!("{}", err);
return;
}
let rand_ttl = rand::thread_rng().gen_range(0..RANDOM_TTL_SIZE);
let expires_time =
(Time::now().as_secs() + 60 * 60 * 24 * 2 + rand_ttl - RANDOM_TTL_SIZE / 2).into();
let object_type = object::ObjectKind::Msg.into();
let version = 1.into();
let stream_number = from_identity.address().stream();
let header = object::Header::new(expires_time, object_type, version, stream_number);
let mut header_bytes = Vec::new();
header.write_to(&mut header_bytes).unwrap();
let msg = content::Msg::new(
&header_bytes,
&from_identity,
&to_identity,
encoding::Encoding::Simple,
simple_bytes,
);
if let Err(err) = msg {
error!("{}", err);
return;
}
let msg = msg.unwrap();
let mut msg_bytes = Vec::new();
msg.write_to(&mut msg_bytes).unwrap();
let public_key = to_identity.public_encryption_key();
let encrypted = crypto::Encrypted::encrypt(msg_bytes, &public_key);
if let Err(err) = encrypted {
error!("{}", err);
return;
}
let encrypted = encrypted.unwrap();
let mut encrypted_bytes = Vec::new();
encrypted.write_to(&mut encrypted_bytes).unwrap();
let msg = object::Msg::new(encrypted_bytes);
let mut payload = Vec::new();
msg.write_to(&mut payload).unwrap();
if let Err(err) = command_sender.send(NodeCommand::Send { header, payload }) {
error!("{}", err);
}
debug!("End send_msg");
logger.info("Message sent");
}
fn send_broadcast(
command_sender: &mut Sender<NodeCommand>,
from_identity: &PrivateIdentity,
subject: &str,
body: &str,
logger: &mut Logger,
) {
debug!("Start send_broadcast");
let simple = encoding::Simple::new(subject.as_bytes().to_vec(), body.as_bytes().to_vec());
if let Err(err) = simple {
error!("{}", err);
return;
}
let simple = simple.unwrap();
let mut simple_bytes = Vec::new();
if let Err(err) = simple.write_to(&mut simple_bytes) {
error!("{}", err);
return;
}
let rand_ttl = rand::thread_rng().gen_range(0..RANDOM_TTL_SIZE);
let expires_time =
(Time::now().as_secs() + 60 * 60 * 24 * 2 + rand_ttl - RANDOM_TTL_SIZE / 2).into();
let object_type = object::ObjectKind::Broadcast.into();
let version = 5.into();
let stream_number = from_identity.address().stream();
let header = object::Header::new(expires_time, object_type, version, stream_number);
let mut header_bytes = Vec::new();
header.write_to(&mut header_bytes).unwrap();
if version.as_u64() == 5 {
from_identity
.address()
.broadcast_tag()
.write_to(&mut header_bytes)
.unwrap();
}
let broadcast = content::Broadcast::new(
&header_bytes,
&from_identity,
encoding::Encoding::Simple,
simple_bytes,
);
if let Err(err) = broadcast {
error!("{}", err);
return;
}
let broadcast = broadcast.unwrap();
let mut broadcast_bytes = Vec::new();
broadcast.write_to(&mut broadcast_bytes).unwrap();
let private_key = from_identity.address().broadcast_private_encryption_key();
if let Err(err) = private_key {
error!("{}", err);
return;
}
let private_key = private_key.unwrap();
let encrypted = crypto::Encrypted::encrypt(broadcast_bytes, &private_key.public_key());
if let Err(err) = encrypted {
error!("{}", err);
return;
}
let encrypted = encrypted.unwrap();
let mut encrypted_bytes = Vec::new();
encrypted.write_to(&mut encrypted_bytes).unwrap();
let broadcast_v5 =
object::BroadcastV5::new(from_identity.address().broadcast_tag(), encrypted_bytes);
let mut payload = Vec::new();
broadcast_v5.write_to(&mut payload).unwrap();
if let Err(err) = command_sender.send(NodeCommand::Send { header, payload }) {
error!("{}", err);
}
debug!("End send_broadcast");
logger.info("Broadcast sent");
}
impl Tab {
#[allow(clippy::too_many_arguments)]
pub(crate) fn set_widgets(
&mut self,
ui: &mut conrod_core::UiCell,
ids: &mut Ids,
config: &GuiConfig,
state: gui::State,
boxes: &mut Option<Boxes>,
command_sender: &mut Sender<NodeCommand>,
logger: &mut Logger,
) {
let text_size = config.text_size();
let font_id = ui.fonts.ids().next().unwrap();
self.sub_tabs.set_widgets(ui, ids, config);
match self.sub_tabs.selected() {
SubTab::Message => {
self.message_sub_tab.set_widgets(ui, ids, config, boxes);
}
SubTab::Broadcast => {
self.broadcast_sub_tab.set_widgets(ui, ids, config, boxes);
}
}
let label = "Clear";
let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
for _click in widget::Button::new()
.top_left_with_margins_on(ids.send_buttons_canvas, 2.0, 2.0)
.w_h(width + 8.0, text_size as f64 + 8.0)
.label_font_size(text_size as u32)
.label(label)
.color(color::DARK_GRAY)
.label_color(color::WHITE)
.set(ids.send_clear_button, ui)
{
self.message_sub_tab.subject_value = String::new();
self.message_sub_tab.body_value = String::new();
self.broadcast_sub_tab.subject_value = String::new();
self.broadcast_sub_tab.body_value = String::new();
}
let label = "Send";
let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
for _click in widget::Button::new()
.right_from(ids.send_clear_button, 4.0)
.w_h(width + 8.0, text_size as f64 + 8.0)
.label_font_size(text_size as u32)
.label(label)
.color(color::RED)
.label_color(color::WHITE)
.set(ids.send_send_button, ui)
{
if state != gui::State::Running {
logger.error("Run node before send message");
continue;
}
if boxes.is_none() {
continue;
}
let boxes = boxes.as_mut().unwrap();
if boxes.selected_identity_index().is_none() {
continue;
}
let index = boxes.selected_identity_index().unwrap();
let from_identity = &boxes.user().private_identities()[index];
match self.sub_tab {
SubTab::Message => {
if boxes.selected_contact_index().is_none() {
continue;
}
let to_index = boxes.selected_contact_index().unwrap();
let to_address = &boxes.user().contacts()[to_index].address().clone();
let to_identity = boxes.user().private_identity_by_address(to_address);
if to_identity.is_none() {
continue;
}
let to_identity = to_identity.unwrap();
if !to_identity.chan() {
logger.error("Currently, restricted to chan");
continue;
}
send_msg(
command_sender,
from_identity,
&to_identity.into(),
&self.message_sub_tab.subject_value,
&self.message_sub_tab.body_value,
logger,
);
boxes.set_selected_identity_index(None);
boxes.set_selected_contact_index(None);
self.message_sub_tab.subject_value = String::new();
self.message_sub_tab.body_value = String::new();
}
SubTab::Broadcast => {
send_broadcast(
command_sender,
from_identity,
&self.broadcast_sub_tab.subject_value,
&self.broadcast_sub_tab.body_value,
logger,
);
boxes.set_selected_identity_index(None);
boxes.set_selected_contact_index(None);
self.broadcast_sub_tab.subject_value = String::new();
self.broadcast_sub_tab.body_value = String::new();
}
}
}
}
}
impl MessageSubTab {
pub(crate) fn set_widgets(
&mut self,
ui: &mut conrod_core::UiCell,
ids: &mut Ids,
config: &GuiConfig,
boxes: &mut Option<Boxes>,
) {
let text_size = config.text_size();
let font_id = ui.fonts.ids().next().unwrap();
if boxes.is_none() {
return;
}
let boxes = boxes.as_mut().unwrap();
widget::Canvas::new()
.flow_down(&[
(
ids.send_message_headers_canvas,
widget::Canvas::new()
.length((text_size as f64 + 8.0 + 2.0) * 3.0 + 2.0)
.color(color::DARK_CHARCOAL)
.pad_bottom(2.0),
),
(
ids.send_message_body_canvas,
widget::Canvas::new()
.scroll_kids_vertically()
.pad_bottom((text_size / 2) as f64),
),
])
.top_left_of(ids.send_tabs_body)
.wh_of(ids.send_tabs_body)
.set(ids.send_message, ui);
let label = "From: ";
let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
widget::Text::new(label)
.top_left_with_margins_on(ids.send_message_headers_canvas, 2.0, 2.0)
.w_h(width + 8.0, text_size as f64 + 8.0)
.font_size(text_size as u32)
.color(color::LIGHT_BLUE)
.set(ids.send_message_from_label, ui);
let label = if let Some(index) = boxes.selected_identity_index() {
let identity = &boxes.user().private_identities()[index];
boxes.user().rich_alias(&identity.to_string())
} else {
"-- Select from Identities tab --".to_string()
};
let width = text::line::width(&label, ui.fonts.get(font_id).unwrap(), text_size as u32);
widget::Text::new(&label)
.right_from(ids.send_message_from_label, 2.0)
.w_h(width + 8.0, text_size as f64 + 8.0)
.font_size(text_size as u32)
.color(color::WHITE)
.set(ids.send_message_from, ui);
let label = "To: ";
let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
widget::Text::new(label)
.down_from(ids.send_message_from_label, 4.0)
.w_h(width + 8.0, text_size as f64 + 8.0)
.font_size(text_size as u32)
.color(color::LIGHT_BLUE)
.set(ids.send_message_to_label, ui);
let label = if let Some(index) = boxes.selected_contact_index() {
let contact = &boxes.user().contacts()[index];
boxes.user().rich_alias(&contact.address().to_string())
} else {
"-- Select from Contacts tab --".to_string()
};
let width = text::line::width(&label, ui.fonts.get(font_id).unwrap(), text_size as u32);
widget::Text::new(&label)
.right_from(ids.send_message_to_label, 2.0)
.w_h(width + 8.0, text_size as f64 + 8.0)
.font_size(text_size as u32)
.color(color::WHITE)
.set(ids.send_message_to, ui);
let label = "Subject: ";
let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
widget::Text::new(label)
.down_from(ids.send_message_to_label, 4.0)
.w_h(width + 8.0, text_size as f64 + 8.0)
.font_size(text_size as u32)
.color(color::LIGHT_BLUE)
.set(ids.send_message_subject_label, ui);
widget::Canvas::new()
.right_from(ids.send_message_subject_label, 2.0)
.w_h(320.0, text_size as f64 + 4.0)
.scroll_kids_vertically()
.color(color::LIGHT_GRAY)
.set(ids.send_message_subject_canvas, ui);
if let Some(edit) = widget::TextEdit::new(&self.subject_value)
.top_left_with_margins_on(ids.send_message_subject_canvas, 2.0, 2.0)
.padded_w_of(ids.send_message_subject_canvas, 2.0)
.parent(ids.send_message_subject_canvas)
.restrict_to_height(false)
.font_size(text_size as u32)
.wrap_by_character()
.color(color::BLACK)
.line_spacing((text_size / 2) as f64)
.set(ids.send_message_subject, ui)
{
self.subject_value = edit;
}
widget::Scrollbar::y_axis(ids.send_message_subject_canvas)
.auto_hide(true)
.set(ids.send_message_subject_scrollbar, ui);
if let Some(edit) = widget::TextEdit::new(&self.body_value)
.top_left_with_margins_on(ids.send_message_body_canvas, 2.0, 2.0)
.padded_w_of(ids.send_message_body_canvas, 2.0)
.parent(ids.send_message_body_canvas)
.restrict_to_height(false)
.font_size(text_size as u32)
.line_spacing((text_size / 2) as f64)
.wrap_by_character()
.set(ids.send_message_body, ui)
{
self.body_value = edit;
}
widget::Scrollbar::y_axis(ids.send_message_body_canvas)
.auto_hide(true)
.set(ids.send_message_body_scrollbar, ui);
}
}
impl BroadcastSubTab {
pub(crate) fn set_widgets(
&mut self,
ui: &mut conrod_core::UiCell,
ids: &mut Ids,
config: &GuiConfig,
boxes: &mut Option<Boxes>,
) {
let text_size = config.text_size();
let font_id = ui.fonts.ids().next().unwrap();
if boxes.is_none() {
return;
}
let boxes = boxes.as_mut().unwrap();
widget::Canvas::new()
.flow_down(&[
(
ids.send_broadcast_headers_canvas,
widget::Canvas::new()
.length((text_size as f64 + 8.0 + 2.0) * 2.0 + 2.0)
.color(color::DARK_CHARCOAL)
.pad_bottom(2.0),
),
(
ids.send_broadcast_body_canvas,
widget::Canvas::new()
.scroll_kids_vertically()
.pad_bottom((text_size / 2) as f64),
),
])
.top_left_of(ids.send_tabs_body)
.wh_of(ids.send_tabs_body)
.set(ids.send_broadcast, ui);
let label = "From: ";
let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
widget::Text::new(label)
.top_left_with_margins_on(ids.send_broadcast_headers_canvas, 2.0, 2.0)
.w_h(width + 8.0, text_size as f64 + 8.0)
.font_size(text_size as u32)
.color(color::LIGHT_BLUE)
.set(ids.send_broadcast_from_label, ui);
let label = if let Some(index) = boxes.selected_identity_index() {
let identity = &boxes.user().private_identities()[index];
boxes.user().rich_alias(&identity.to_string())
} else {
"-- Select from Identities tab --".to_string()
};
let width = text::line::width(&label, ui.fonts.get(font_id).unwrap(), text_size as u32);
widget::Text::new(&label)
.right_from(ids.send_broadcast_from_label, 2.0)
.w_h(width + 8.0, text_size as f64 + 8.0)
.font_size(text_size as u32)
.color(color::WHITE)
.set(ids.send_broadcast_from, ui);
let label = "Subject: ";
let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
widget::Text::new(label)
.down_from(ids.send_broadcast_from_label, 4.0)
.w_h(width + 8.0, text_size as f64 + 8.0)
.font_size(text_size as u32)
.color(color::LIGHT_BLUE)
.set(ids.send_broadcast_subject_label, ui);
widget::Canvas::new()
.right_from(ids.send_broadcast_subject_label, 2.0)
.w_h(320.0, text_size as f64 + 4.0)
.scroll_kids_vertically()
.color(color::LIGHT_GRAY)
.set(ids.send_broadcast_subject_canvas, ui);
if let Some(edit) = widget::TextEdit::new(&self.subject_value)
.top_left_with_margins_on(ids.send_broadcast_subject_canvas, 2.0, 2.0)
.padded_w_of(ids.send_broadcast_subject_canvas, 2.0)
.parent(ids.send_broadcast_subject_canvas)
.restrict_to_height(false)
.font_size(text_size as u32)
.wrap_by_character()
.color(color::BLACK)
.line_spacing((text_size / 2) as f64)
.set(ids.send_broadcast_subject, ui)
{
self.subject_value = edit;
}
widget::Scrollbar::y_axis(ids.send_broadcast_subject_canvas)
.auto_hide(true)
.set(ids.send_broadcast_subject_scrollbar, ui);
if let Some(edit) = widget::TextEdit::new(&self.body_value)
.top_left_with_margins_on(ids.send_broadcast_body_canvas, 2.0, 2.0)
.padded_w_of(ids.send_broadcast_body_canvas, 2.0)
.parent(ids.send_broadcast_body_canvas)
.restrict_to_height(false)
.font_size(text_size as u32)
.line_spacing((text_size / 2) as f64)
.wrap_by_character()
.set(ids.send_broadcast_body, ui)
{
self.body_value = edit;
}
widget::Scrollbar::y_axis(ids.send_broadcast_body_canvas)
.auto_hide(true)
.set(ids.send_broadcast_body_scrollbar, ui);
}
}