koibumi-daemon-sync 0.0.1

An experimental Bitmessage client daemon (sync version)
//! __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-sync`](https://crates.io/crates/koibumi-sync) instead.
//!
//! # Usage
//!
//! To install the Koibumi Bitmessage client daemon, issue the command:
//!
//! ```sh
//! cargo install koibumi-daemon-sync
//! ```
//!
//! To run the client, run `koibumi-daemon-sync` 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 log::{error, info};

use koibumi_common_sync::{boxes::Boxes, param::Params};
use koibumi_core::{address::Address, message};
use koibumi_node_sync::{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();
    match boxes.manager().insert_msg(user_id, identity, object) {
        Ok(message) => {
            // XXX test
            println!("From: {}", message.from_address().to_string());
            println!("{}", String::from_utf8_lossy(message.content()).to_string());
        }
        Err(err) => {
            error!("{}", err);
        }
    }
}

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

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

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

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

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

    let (command_sender, 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},
            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();
                sender.send(Command::Stop).unwrap_or_else(|err| {
                    error!("{}", err);
                });
            }
            1 => {
                let mut sender = sender.clone();
                sender.send(Command::Abort).unwrap_or_else(|err| {
                    error!("{}", err);
                });
            }
            _ => std::process::exit(0),
        })
        .unwrap_or_else(|err| {
            error!("{}", err);
        });
    }

    let sender = command_sender;
    let response = {
        let path = koibumi_common_sync::node::prepare(&params).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(), path, users)) {
            error!("{}", err);
            return;
        }
        response_receiver.recv()
    };
    if response.is_err() {
        error!("Could not start node.");
        std::process::exit(1)
    }
    let Response::Started(receiver) = response.unwrap();

    while let Ok(event) = receiver.recv() {
        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);
                }
            }
        }
    }

    if let Err(err) = node_handle.join() {
        error!("{:?}", err);
    }
}