koibumi-sync 0.0.1

An experimental Bitmessage client (sync version)
//! __Koibumi__ is an experimental [Bitmessage](https://bitmessage.org/) client.
//! Note that Koibumi is __NOT__ an official project of The Bitmessage Developers.
//!
//! # Features
//!
//! Koibumi can connect to the Bitmessage network
//! and relay Bitmessage objects.
//!
//! It has a GUI.
//! Configuration and monitoring can be performed on the window.
//! If you do not need any GUI, use
//! [`koibumi-daemon-sync`](https://crates.io/crates/koibumi-daemon-sync) instead.
//!
//! Currently, this client can send and receive chan and broadcast messages.
//!
//! By default, network connections are limited to __via Tor only__.
//! In this case, you need a Tor SOCKS5 proxy running at localhost.
//!
//! The objects loaded from the network and
//! the list of known node addresses are saved on local file system by using SQLite.
//! Configurations can be saved to the file in the user's configuration directory.
//! The data directory can be changed by specifying a `-d` option on the command line.
//!
//! # Usage
//!
//! To install the Koibumi Bitmessage client, issue the command:
//!
//! ```sh
//! cargo install koibumi-sync
//! ```
//!
//! To run the client, run `koibumi-sync` command, and a GUI window popups.
//! On the window, you can configure several settings.
//!
//! To connect to the Bitmessage network, push __Start__ button on the window.
//! When the client connected to some remote nodes,
//! their addresses and user agents are shown on the window.
//! You can monitor how many Bitmessage objects are downloaded and shared.
//!
//! This client is experimental and under development,
//! so many debug logs are printed on the console.
//! Adding `-v` option on the command line, more messages are printed.
//!
//! Note that since database format can be changed among versions,
//! you may have to remove database files located at `$HOME/.config/koibumi` when trying new version.
//!
//! # Settings
//!
//! These are default settings:
//!
//! * Spawn a Bitmessage server at 127.0.0.1:8444.
//! But, currently, this does not fully work,
//! and the client does not advertise its server address.
//! * Connect to the Bitmessage network via SOCKS5 proxy at 127.0.0.1:9050.
//! It is the default local Tor proxy server address.
//! Be careful, if this checkbox is turned off, no SOCKS proxy is used,
//! and all connections are directly to Clearnet.
//! * The client can connect to Bitmessage nodes that have Onion addresses.
//! * The client can connect to Bitmessage nodes that have IP addresses.
//! Be careful, if SOCKS checkbox above is turned off,
//! then all connections are directly to Clearnet.
//! * Does not connect to myself.
//! * Use a user agent of a version of PyBitmessage.
//! * At first, connect to the seed Bitmessage node on the Tor network.
//! This default address is embedded in PyBitmessage source code.
//! * Max 160 incoming connections can be accepted
//! and max 128 incoming established connections can be managed.
//! * Max 32 outgoing connections can be initiated
//! and max 8 outgoing established connections are keeped.
//!
//! You can change these settings on the GUI window.
//!
//! # Implementation details
//!
//! Some of the significant external crates which Koibumi uses:
//!
//! * [`conrod_core`](https://crates.io/crates/conrod_core) for GUI
//!
//! Koibumi's internal implementation is divided into several crates:
//!
//! * Applications
//!   * [`koibumi-sync`](https://crates.io/crates/koibumi-sync):
//!     An experimental Bitmessage client with GUI
//!   * [`koibumi-daemon-sync`](https://crates.io/crates/koibumi-daemon-sync):
//!     An experimental Bitmessage client without GUI
//! * Libraries
//!   * [`koibumi-core`](https://crates.io/crates/koibumi-core):
//!     A library that provides various types and methods
//!     usable to implement Bitmessage clients
//!   * [`koibumi-node-sync`](https://crates.io/crates/koibumi-node-sync):
//!     An implementation of Bitmessage network node
//! * Other small libraries
//!   * [`koibumi-base32`](https://crates.io/crates/koibumi-base32):
//!     Minimal Base32 encoder/decoder for Onion addresses
//!   * [`koibumi-socks`](https://crates.io/crates/koibumi-socks):
//!     Minimal SOCKS5 client for Tor proxy

#![deny(unsafe_code)]
#![allow(dead_code)]
#![recursion_limit = "256"]

mod config;
mod contacts;
mod gui;
mod identities;
mod ids;
mod log;
mod messages;
mod send;
mod settings;
mod status;
mod subscriptions;
mod tab;

use ::log::error;
use glium::Surface;

use gui::Gui;
use ids::Ids;

use koibumi_node_sync::Event as BmEvent;

const TITLE: &str = "Koibumi - An Experimental Bitmessage Client";
const WIDTH: u32 = 512;
const HEIGHT: u32 = 512;

fn main() {
    let event_loop = glium::glutin::event_loop::EventLoop::<BmEvent>::with_user_event();

    let mut gui = Gui::new(event_loop.create_proxy());

    let window = glium::glutin::window::WindowBuilder::new()
        .with_title(TITLE)
        .with_inner_size(glium::glutin::dpi::LogicalSize::new(WIDTH, HEIGHT));
    let context = glium::glutin::ContextBuilder::new()
        .with_vsync(true)
        .with_multisampling(4);
    let display = glium::Display::new(window, context, &event_loop);
    if let Err(err) = display {
        error!("{}", err);
        return;
    }
    let display = display.unwrap();

    // construct our `Ui`.
    let mut ui = conrod_core::UiBuilder::new([WIDTH as f64, HEIGHT as f64]).build();

    // Generate the widget identifires.
    let mut ids = Ids::new(ui.widget_id_generator());

    // Add a `Font` to the `Ui`'s `font::Map` from bytes.
    let font = rusttype::Font::from_bytes(koibumi_unifont_jp::TTF);
    if let Err(err) = font {
        error!("{}", err);
        return;
    }
    let font = font.unwrap();
    let _font_id = ui.fonts.insert(font);

    // A type used for converting `conrod_core::render::Primitives` into `Command`s that can be used
    // for drawing to the glium `Surface`.
    let renderer = conrod_glium::Renderer::new(&display);
    if let Err(err) = renderer {
        error!("{}", err);
        return;
    }
    let mut renderer = renderer.unwrap();

    // The image map describing each of our widget->image mappings (in our case, none).
    let image_map = conrod_core::image::Map::<glium::texture::Texture2d>::new();

    let mut should_update_ui = true;
    event_loop.run(move |event, _, control_flow| {
        *control_flow = glium::glutin::event_loop::ControlFlow::Poll;
        *control_flow = glium::glutin::event_loop::ControlFlow::Wait;

        // Break from the loop upon `Escape` or closed window.
        match &event {
            glium::glutin::event::Event::WindowEvent { event, .. } => match event {
                // Break from the loop upon `Escape`.
                glium::glutin::event::WindowEvent::CloseRequested
                | glium::glutin::event::WindowEvent::KeyboardInput {
                    input:
                        glium::glutin::event::KeyboardInput {
                            virtual_keycode: Some(glium::glutin::event::VirtualKeyCode::Escape),
                            ..
                        },
                    ..
                } => *control_flow = glium::glutin::event_loop::ControlFlow::Exit,
                _ => {}
            },
            glium::glutin::event::Event::UserEvent(event) => {
                match event {
                    BmEvent::ConnectionCounts {
                        incoming_initiated,
                        incoming_connected,
                        incoming_established,
                        outgoing_initiated,
                        outgoing_connected,
                        outgoing_established,
                    } => {
                        gui.status_tab.incoming_initiated = *incoming_initiated;
                        gui.status_tab.incoming_connected = *incoming_connected;
                        gui.status_tab.incoming_established = *incoming_established;
                        gui.status_tab.outgoing_initiated = *outgoing_initiated;
                        gui.status_tab.outgoing_connected = *outgoing_connected;
                        gui.status_tab.outgoing_established = *outgoing_established;
                    }
                    BmEvent::AddrCount(count) => {
                        gui.status_tab.addr_count = *count;
                    }
                    BmEvent::Established {
                        addr,
                        user_agent,
                        rating,
                    } => {
                        gui.status_tab.peers.push(addr.clone());
                        gui.status_tab
                            .peer_infos
                            .insert(addr.clone(), (user_agent.clone(), rating.clone()));
                    }
                    BmEvent::Disconnected { addr } => {
                        if let Some(index) = gui.status_tab.peers.iter().position(|v| v == addr) {
                            gui.status_tab.peers.remove(index);
                        }
                    }
                    BmEvent::Objects {
                        missing,
                        loaded,
                        uploaded,
                    } => {
                        gui.status_tab.missing_objects = *missing;
                        gui.status_tab.loaded_objects = *loaded;
                        gui.status_tab.uploaded_objects = *uploaded;
                    }
                    BmEvent::Stopped => {
                        gui.state = crate::gui::State::Idle;
                    }
                    BmEvent::Msg {
                        user_id,
                        address,
                        object,
                    } => {
                        gui.handle_msg(user_id.clone(), address.clone(), object.clone());
                    }
                    BmEvent::Broadcast {
                        user_id,
                        address,
                        object,
                    } => {
                        gui.handle_broadcast(user_id.clone(), address.clone(), object.clone());
                    }
                }
                should_update_ui = true;
            }
            _ => {}
        }

        // Use the `winit` backend feature to convert the winit event to a conrod one.
        if let Some(event) =
            conrod_winit::v023_convert_event!(&event, &display.gl_window().window())
        {
            ui.handle_event(event);
            should_update_ui = true;
        }

        match &event {
            glium::glutin::event::Event::MainEventsCleared => {
                if should_update_ui {
                    should_update_ui = false;

                    // Set the widgets.
                    gui.set_widgets(&mut ui.set_widgets(), &mut ids);

                    // Request redraw if the `Ui` has changed.
                    display.gl_window().window().request_redraw();
                }
            }
            glium::glutin::event::Event::RedrawRequested(_) => {
                // Draw the `Ui` if it has changed.
                let primitives = ui.draw();

                renderer.fill(&display, primitives, &image_map);
                let mut target = display.draw();
                target.clear_color(0.0, 0.0, 0.0, 1.0);
                if let Err(err) = renderer.draw(&display, &mut target, &image_map) {
                    error!("{}", err);
                    return;
                }
                if let Err(err) = target.finish() {
                    error!("{}", err);
                    return;
                }
            }
            _ => {}
        }
    })
}