torchbear 0.11.2

Lua programming environment in Rust
Documentation
#[macro_use] extern crate failure;
#[macro_use] extern crate failure_derive;
#[macro_use] extern crate log;
#[macro_use] extern crate human_panic;
#[macro_use] extern crate serde_derive;
#[cfg(feature = "tantivy_bindings")]
extern crate tantivy;

pub mod bindings;
pub mod logger;
pub mod conf;
pub mod error;

use actix::prelude::*;
use actix_lua::LuaActorBuilder;
use actix_web::{server as actix_server, App};
use rlua::prelude::*;
use std::{
    path::{Path, PathBuf},
    result
};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
use serde_json::Value;
use crate::error::Error;

type LuaAddr = ::actix::Addr<::actix_lua::LuaActor>;
pub type Result<T> = result::Result<T, Error>;

#[derive(Clone)]
pub struct AppState {
    pub lua: Option<LuaAddr>,
    pub init_path: PathBuf,
    pub init_args: Option<Vec<String>>,
    pub package_path: Option<String>,
    pub settings: Value,
}

impl AppState {
    pub fn create_vm (&self) -> result::Result<Lua, LuaError> {
        let lua = unsafe { Lua::new_with_debug() };

        lua.exec::<_, ()>(include_str!("handlers/debug.lua"), None)?;

        bindings::app::init(&lua).map_err(LuaError::external)?;
        bindings::archive::init(&lua).map_err(LuaError::external)?;
        bindings::crypto::init(&lua).map_err(LuaError::external)?;
        bindings::string::init(&lua).map_err(LuaError::external)?;
        bindings::system::init(&lua).map_err(LuaError::external)?;
        bindings::text::init(&lua).map_err(LuaError::external)?;
        bindings::web::init(&lua).map_err(LuaError::external)?;

        // torchbear global table
        {
            let tb_table = lua.create_table()?;
            tb_table.set("settings", rlua_serde::to_value(&lua, &self.settings).map_err(LuaError::external)?)?;
            tb_table.set("init_filename", self.init_path.to_str())?;
            tb_table.set("version", env!("CARGO_PKG_VERSION"))?;
            let os = if cfg!(target_os = "windows") {
                "windows"
            } else if cfg!(target_os = "linux") {
                "linux"
            } else if cfg!(target_os = "macos") {
                "macos"
            } else if cfg!(target_os = "android") {
                "android"
            } else {
                "unknown"
            };
            tb_table.set("os", os)?;
            lua.globals().set("torchbear", tb_table)?;
        }

        // Lua package.path
        match self.package_path {
            Some(ref package_path) => {
                let package: LuaTable = lua.globals().get("package")?;
                let mut path: String = package.get("path")?;
                path.push_str(";");
                path.push_str(package_path);
                package.set("path", path)?;
            },
            None => ()
        }

        // Lua arg
        match self.init_args {
            Some(ref init_args) => lua.globals().set("arg", lua.create_sequence_from(init_args.clone())?)?,
            None => ()
        }

        // Lua Bridge
        lua.exec::<_, ()>(include_str!("handlers/bridge.lua"), None)?;

        Ok(lua)
    }

    pub fn create_addr (&self) -> LuaAddr {
        let vm = self.create_vm().unwrap();
        Arbiter::start(move |_| {
            let lua_actor = LuaActorBuilder::new()
                .on_handle_with_lua(include_str!("handlers/web_server.lua"))
                .build_with_vm(vm)
                .unwrap();
            lua_actor
        })
    }
}

pub struct ApplicationBuilder {
    log_settings: logger::Settings,
}

#[derive(Debug, Default, Deserialize)]
pub struct SettingConfig {
    general: Option<Value>,
    #[serde(rename = "web-server")]
    web_server: Option<Value>,
}

impl ApplicationBuilder {
    pub fn new () -> Self {
        Self {
            log_settings: logger::Settings{
                level: logger::LevelFilter::Info,
                everything: false,
            }
        }
    }

    pub fn log_level (&mut self, level: logger::Level) -> &mut Self {
        self.log_settings.level = level.to_level_filter(); self
    }

    pub fn log_everything (&mut self, b: bool) -> &mut Self {
        self.log_settings.everything = b; self
    }

    pub fn start (&mut self, args: Option<Vec<String>>) -> Result<()> {
        
        setup_panic!();

        let mut init_path: Option<PathBuf> = None;
        let mut init_args: Option<Vec<String>> = None;
        let mut package_path: Option<String> = None;

        match args {
            // Interpreter
            Some(args) => {

                init_path = Path::new(args.first().expect("Missing first argument."))
                    .canonicalize()
                    .map_err(|e| {
                        println!("Error getting the absolute path: {}", e);
                        std::process::exit(1);
                    })
                    .ok();


                init_args = Some(args.to_vec());

                package_path = match &init_path {
                    Some(p) => p.parent().map(|p| {
                            let mut t = p.to_str().expect("Error getting the directory.").to_string();
                            t.push_str("/?.lua"); t
                    }),
                    None => None
                };
            },
            // Server
            None => ()
        }

        let scl_path = match &init_path {
            Some(p) => p.parent().unwrap_or(Path::new(".")).join("torchbear.scl"),
            None => PathBuf::from("torchbear.scl"),
        };

        let setting_file = scl_path.as_path();

        let config = if setting_file.exists() {
            conf::Conf::load_file(&setting_file)?
        } else {
            SettingConfig::default()
        };

        fn get_or (map: &Value, key: &str, val: &str) -> String {
            map.get(key).map(|s| String::from(s.as_str().unwrap_or(val)) ).unwrap_or(String::from(val))
        }
        
        let general = config.general.unwrap_or_default();

        let init_path = init_path.unwrap_or(Path::new(&get_or(&general, "init", "init.lua")).to_path_buf());
        let log_path = get_or(&general, "log_path", "log");
        
        if !init_path.exists() {
            println!("Error: Specified init.lua not found. You may have not completed installing your app");
            std::process::exit(1);
        }

        logger::init(::std::path::Path::new(&log_path), self.log_settings.clone());

        let sys = actix::System::new("torchbear");

        let mut app_state = AppState {
            lua: None,
            init_path: init_path,
            init_args: init_args,
            package_path: package_path,
            settings: general
        };

        if let Some(web) = config.web_server {

            if let Some(Some(bootstrap)) = web.get("bootstrap_path").map(|s| { s.as_str() }) {
                let vm = app_state.create_vm().unwrap();
                vm.globals().get::<_, LuaTable>("torchbear").unwrap().set("bootstrap", bootstrap).unwrap();

                if !vm.exec::<_, bool>(include_str!("handlers/bootstrap.lua"), Some("bootstrap")).unwrap()
                { std::process::exit(1); }
            }

            let single_actor = match web.get("single_actor").map(|s| { s.as_bool() }) {
                None => false,
                Some(Some(b)) => b,
                _ => {
                    println!("Error: Setting web_server.single_actor must be a boolean value");
                    std::process::exit(1);
                },
            };

            if single_actor {
                app_state.lua = Some(app_state.create_addr());
            }

            log::debug!("web server section in settings, starting seting up web server");
            let host = get_or(&web, "address", "0.0.0.0");
            let port = get_or(&web, "port", "3000").parse().unwrap_or(3000);

            let some_ssl = match (web.get("tls_private"), web.get("tls_certificate")) {
                (None, None) => None,
                (Some(priv_path), Some(cert_path)) => {
                    let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
                    builder.set_private_key_file(priv_path.as_str().unwrap(), SslFiletype::PEM).unwrap();
                    builder.set_certificate_chain_file(cert_path.as_str().unwrap()).unwrap();
                    Some(builder)
                },
                _ => {
                    println!("Error: SSL needs both tls_private and tls_certificate settings.");
                    std::process::exit(1);
                }
            };

            let mut server = actix_server::new(move || {
                App::with_state(app_state.clone())
                    .default_resource(|r| r.with(bindings::web::server::handler))
            });

            server = server.bind((host.as_str(), port))?;
            log::debug!("web server listening on port {}:{}", &host, port);

            if let Some(ssl_builder) = some_ssl {
                let host = get_or(&web, "tls_address", "0.0.0.0");
                let port = get_or(&web, "tls_port", "3001").parse().unwrap_or(3001);
                server = server.bind_ssl((host.as_str(), port), ssl_builder)?;
                log::debug!("tls server listening on port {}:{}", &host, port);
            }

            server.start();

            let _ = sys.run();
        } else {
            // Temporary fix to run non webserver apps. Doesn't start the actor
            // system, just runs a vanilla lua vm.
            debug!("Torchbear app started");
            let _ = app_state.create_vm()?;
        }

        Ok(())
    }
}