koibumi-daemon 0.0.9

An experimental Bitmessage client daemon
//! __Koibumi daemon__ is an experimental [Bitmessage](https://bitmessage.org/) client daemon.
//! Note that Koibumi daemon is __NOT__ an official project of The Bitmessage Developers.
//!
//! # Features
//!
//! Koibumi daemon can connect to the Bitmessage network
//! and relay Bitmessage objects.
//!
//! Currently, this client can 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 loaded from the file saved by the GUI version of Koibumi.
//! The data directory can be changed by specifying a `-d` option on the command line.
//!
//! Koibumi daemon does not have any GUI.
//! If you need a GUI, use
//! [`koibumi`](https://crates.io/crates/koibumi) instead.
//!
//! # Usage
//!
//! To install the Koibumi Bitmessage client daemon, issue the command:
//!
//! ```sh
//! cargo install koibumi-daemon
//! ```
//!
//! To run the client, run `koibumi-daemon` command.
//!
//! 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.
//!
//! ## Optional features
//!
//! If you enable `ctrlc` feature, you can control termination by Ctrl-C key.
//! To stop gracefully, push Ctrl-C on the console and wait for tasks to exit.
//! If you can not wait, push Ctrl-C once more to abort tasks.
//! If they are not aborted, push Ctrl-C yet once more to terminate abruptly.

#![deny(unsafe_code)]
#![allow(dead_code)]

use async_std::task;
use futures::{sink::SinkExt, stream::StreamExt};
use log::{error, info};

use koibumi_common::{boxes::Boxes, param::Params};
use koibumi_core::{address::Address, message};
use koibumi_node::{self as node, Command, Event, Response};

fn handle_msg(boxes: &Boxes, user_id: Vec<u8>, address: Address, object: message::Object) {
    let identity = boxes.user().private_identity_by_address(&address);
    if identity.is_none() {
        error!("identity not found for address: {}", address);
        return
    }
    let identity = identity.unwrap();
    task::block_on(async {
        match boxes.manager().insert_msg(user_id, identity, object).await {
            Ok(message) => {
                // XXX test
                println!("From: {}", message.from_address().to_string());
                println!("{}", String::from_utf8_lossy(message.content()).to_string());
            }
            Err(err) => {
                error!("{}", err);
                return;
            }
        }
    });
}

fn handle_broadcast(boxes: &Boxes, user_id: Vec<u8>, address: Address, object: message::Object) {
    task::block_on(async {
        match boxes
            .manager()
            .insert_broadcast(user_id, address, object)
            .await
        {
            Ok(message) => {
                // XXX test
                println!("From: {}", message.from_address().to_string());
                println!("{}", String::from_utf8_lossy(message.content()).to_string());
            }
            Err(err) => {
                error!("{}", err);
                return;
            }
        }
    });
}

/// The main entry point of the application.
fn main() {
    let params = Params::new();

    koibumi_common::log::init(&params).unwrap_or_else(|err| {
        println!("Warning: Failed to initialize logger.");
        println!("{}", err);
    });

    let config = koibumi_common::config::load(&params).unwrap_or_else(|err| {
        error!("Failed to load config file: {}", err);
        std::process::exit(1)
    });

    let mut boxes = match task::block_on(koibumi_common::boxes::prepare(&params)) {
        Ok(boxes) => Some(boxes),
        Err(err) => {
            error!("{}", err);
            None
        }
    };

    let (command_sender, mut response_receiver, node_handle) = node::spawn();

    info!("Start");
    if boxes.is_none() {
        error!("No boxes");
        std::process::exit(1)
    }

    #[cfg(feature = "ctrlc")]
    {
        use std::sync::atomic::{AtomicUsize, Ordering};

        use async_std::sync::Arc;

        let sender = command_sender.clone();
        let ctrlc_count = Arc::new(AtomicUsize::new(0));
        let cc = Arc::clone(&ctrlc_count);
        ctrlc::set_handler(move || match cc.fetch_add(1, Ordering::SeqCst) {
            0 => {
                let mut sender = sender.clone();
                task::block_on(async {
                    sender.send(Command::Stop).await.unwrap_or_else(|err| {
                        error!("{}", err);
                    });
                });
            }
            1 => {
                let mut sender = sender.clone();
                task::block_on(async {
                    sender.send(Command::Abort).await.unwrap_or_else(|err| {
                        error!("{}", err);
                    });
                });
            }
            _ => std::process::exit(0),
        })
        .unwrap_or_else(|err| {
            error!("{}", err);
        });
    }

    let mut sender = command_sender;
    let response = task::block_on(async {
        let pool = koibumi_common::node::prepare(&params)
            .await
            .unwrap_or_else(|err| {
                error!("{}", err);
                std::process::exit(1);
            });

        let users = vec![boxes.as_ref().unwrap().user().clone().into()];

        if let Err(err) = sender
            .send(Command::Start(config.into(), pool, users))
            .await
        {
            error!("{}", err);
            return None;
        }
        response_receiver.next().await
    });
    if response.is_none() {
        error!("Could not start node.");
        std::process::exit(1)
    }
    let Response::Started(mut receiver) = response.unwrap();

    task::block_on(async {
        while let Some(event) = receiver.next().await {
            match event {
                Event::ConnectionCounts { .. } => (),
                Event::AddrCount(_count) => (),
                Event::Established {
                    addr,
                    user_agent,
                    rating,
                } => {
                    info!("established: {} {} rating:{}", addr, user_agent, rating);
                }
                Event::Disconnected { addr } => {
                    info!("disconnected: {}", addr);
                }
                Event::Objects { .. } => (),
                Event::Stopped => {
                    boxes = None;
                    std::process::exit(0);
                }
                Event::Msg {
                    user_id,
                    address,
                    object,
                } => {
                    if let Some(boxes) = &boxes {
                        handle_msg(boxes, user_id, address, object);
                    }
                }
                Event::Broadcast {
                    user_id,
                    address,
                    object,
                } => {
                    if let Some(boxes) = &boxes {
                        handle_broadcast(boxes, user_id, address, object);
                    }
                }
            }
        }

        node_handle.await;
    });
}