new-home-application 1.0.2

New Home iot application framework. Meant to build application for the New Home Core
Documentation
//! In this module there is the main framework/application part.
//! All the logic for receiving data and calling methods is stored here.

use std::collections::HashMap;
use std::io::{BufWriter, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::Arc;

use actix::{Arbiter, System};
use serde::{Deserialize, Serialize};

use crate::communication::{MethodCall, MethodResult};
use crate::method;
use crate::method::MethodCallable;

pub type MethodFactory = Box<dyn Fn() -> Box<dyn MethodCallable>>;

pub type Application = Arc<InnerApplication>;

/// This struct is the information holder for the application.
/// IT contains mainly the registered methods and does the main routing for calls.
#[derive(Default)]
pub struct InnerApplication {
    methods: HashMap<String, MethodFactory>,
}

/// This struct contains all the application functions that need more than just a read-only reference
/// to the application.
#[derive(Default)]
pub struct ApplicationBuilder(InnerApplication);

impl ApplicationBuilder {
    pub fn new() -> Self {
        Default::default()
    }

    /// Registers a new method to the application.
    /// The method will be build using the given factory when it is called.
    pub fn with(&mut self, method_factory: MethodFactory) {
        let method = (method_factory)();

        println!("Registering method {}", method.name());

        self.0.methods.insert(method.name(), method_factory);
    }

    /// Runs all the parts of the application:
    /// - the TCP Server used to handle requests for the outside
    /// - the handling system which is used to handle incoming clients
    ///
    /// # Errors
    ///
    /// - If the inner actix system crashes
    /// - If the server thread could not be joined correctly
    ///
    pub fn run(self, server_address: String) -> method::Result<()> {
        println!("Starting application");

        let application = Arc::new(self.0);
        let sys = System::new("application");
        let mut arbiter = Arbiter::new();
        let thread = std::thread::spawn(move || {
            InnerApplication::start_server(application, &mut arbiter, server_address)
                .unwrap_or_default()
        });

        println!("Running application system");
        sys.run()?;
        println!("Application system stopped");

        thread.join()?;
        println!("Application server stopped");

        Ok(())
    }
}

impl InnerApplication {
    /// Dispatches a [MethodCall].
    ///
    /// This will always return a Result which can be send back to the client. However it may contain
    /// an error response. You can check this by reading the `code` field.
    pub fn call(&self, context: MethodCall) -> MethodResult {
        if let Some(factory) = self.methods.get(&context.method_name) {
            let mut method: Box<dyn MethodCallable> = (factory)();

            return match method.set_arguments(context.arguments.clone()) {
                Err(error) => MethodResult {
                    code: 1,
                    message: json!({ "error": format!("{}", error) }),
                },
                _ => method.call(context),
            };
        }

        MethodResult {
            code: 1,
            message: json!({
                "error": "Could not find requested method"
            }),
        }
    }

    fn start_server(
        application: Application,
        arbiter: &mut Arbiter,
        server_address: String,
    ) -> method::Result<()> {
        println!("Running application server");

        let server = TcpListener::bind(server_address)?;

        loop {
            if let Ok((client, _addr)) = server.accept() {
                arbiter.send(Box::pin(InnerApplication::handle_client(
                    application.clone(),
                    client,
                )));
            }
        }
    }

    async fn handle_client(application: Application, client: TcpStream) {
        let read_client = match client.try_clone() {
            Ok(client) => client,
            _ => return,
        };

        let mut deserializer = serde_json::Deserializer::from_reader(read_client);
        let context = MethodCall::deserialize(&mut deserializer).unwrap_or_default();
        let result = application.call(context);

        let writer = BufWriter::new(client);

        let mut serializer = serde_json::Serializer::new(writer);
        result.serialize(&mut serializer).unwrap_or_default();
        let mut writer = serializer.into_inner();
        writer.write_all("\n".as_bytes()).unwrap_or_default();
        writer.flush().unwrap_or_default();
    }
}

unsafe impl Send for InnerApplication {}

unsafe impl Sync for InnerApplication {}