<commit_before>#![allow(non_snake_case)]
use core::prelude::*;
use core::{mem, ptr, raw};
use collections::str::StrAllocating;
use collections::string::String;
use libc::{c_char, c_void, size_t};

use lua::lib::raw::*;
use lua::aux::raw::*;
use lua::raw::*;
use luajit::*;
use luajit_constants::*;

use glcommon::GLResult;
use log::{logi, loge};

use lua_callbacks::LuaCallbackType;

static mut GLDRAW_LUA_SANDBOX: *mut c_void = 0 as *mut c_void;
static mut GLDRAW_LUA_STOPFNS: *mut c_void = 0 as *mut c_void;
static mut gldraw_lua_key: i32 = 0;
static LUA_FFI_SCRIPT: &'static str = include_str!("../includes/lua/ffi_loader.lua");
static LUA_RUNNER: &'static str = include_str!("../includes/lua/lua_runner.lua");
static DEFAULT_SCRIPT: &'static str = include_str!("../includes/lua/default_interpolator.lua");

static mut STATIC_LUA: Option<*mut lua_State> = None;

type ReaderState<'a> = (&'a str, bool);

enum SandboxMode {
    Sandboxed(*mut c_void),
    Unsandboxed,
}

#[allow(unused)]
extern "C" fn stringreader(L: *mut lua_State, data: *mut c_void, size: *mut size_t) -> *const c_char {
    unsafe {
        let state: &mut ReaderState = mem::transmute(data);
        let (ref chars, ref mut done) = *state;
        if *done {
            ptr::null()
        } else {
            *done = true;
            *size = chars.len() as u32;
            chars.as_ptr() as *const i8
        }
    }
}

unsafe fn err_to_str(L: *mut lua_State) -> String {
    let mut size: size_t = 0;
    let strptr = lua_tolstring(L, -1, &mut size);
    let luastr: &str = mem::transmute(raw::Slice { data: strptr, len: size as uint });
    let result = luastr.into_string();
    lua_pop(L, -1);
    result
}

unsafe fn runstring(L: *mut lua_State, s: &str, filename: *const i8, env: SandboxMode) -> bool {
    let mut state: ReaderState = (s, false);
    let stateref: *mut c_void = mem::transmute(&mut state);
    if 0 != lua_load(L, stringreader, stateref, filename) {
        false
        //Err(format!("script failed to load: {}", err_to_str(L)))
    } else {
        if let Sandboxed(key) = env {
            lua_pushlightuserdata(L, key);
            lua_gettable(L, LUA_REGISTRYINDEX);
            lua_setfenv(L, -2);
        }
        if 0 != lua_pcall(L, 0, MULTRET, 0) {
            false
            //Err(format!("script failed to run: {}", err_to_str(L)));
        } else {
            true
            //Ok(())
        }
    }
}

unsafe fn init_lua() -> GLResult<*mut lua_State> {
    GLDRAW_LUA_SANDBOX = &mut GLDRAW_LUA_SANDBOX as *mut *mut c_void as *mut c_void;
    GLDRAW_LUA_STOPFNS = &mut GLDRAW_LUA_STOPFNS as *mut *mut c_void as *mut c_void;

    let L = luaL_newstate();
    luaL_openlibs(L);

    luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE as i32|LUAJIT_MODE_ON as i32);

    if runstring(L, LUA_FFI_SCRIPT, cstr!("built-in ffi init script"), Unsandboxed) {
        logi!("ffi init script loaded");
        lua_pushlightuserdata(L, GLDRAW_LUA_SANDBOX);
        lua_getglobal(L, cstr!("sandboxed"));
        lua_settable(L, LUA_REGISTRYINDEX);

        lua_pushlightuserdata(L, GLDRAW_LUA_STOPFNS);
        lua_newtable(L);
        lua_settable(L, LUA_REGISTRYINDEX);
        Ok(L)
    } else {
        let err = format!("ffi init script failed to load: {}\nThis should never happen!", err_to_str(L));
        lua_close(L);
        log_err(err)
    }
}

unsafe fn get_lua() -> GLResult<*mut lua_State> {
    match STATIC_LUA {
        Some(x) => Ok(x),
        None => {
            STATIC_LUA = Some(try!(init_lua()));
            get_lua()
        }
    }
}

#[inline(always)]
pub unsafe fn get_existing_lua() -> Option<*mut lua_State> {
    STATIC_LUA
}

unsafe fn push_sandbox(L: *mut lua_State) {
    lua_pushlightuserdata(L, GLDRAW_LUA_SANDBOX);
    lua_gettable(L, LUA_REGISTRYINDEX);
}

pub unsafe fn load_lua_script(script: Option<&str>) -> GLResult<i32> {
    let L = try!(get_lua());
    logi!("got lua");

    push_sandbox(L);

    lua_pushnil(L);
    lua_setfield(L, -2, cstr!("main"));
    lua_pushnil(L);
    lua_setfield(L, -2, cstr!("onframe"));
    lua_pushnil(L);
    lua_setfield(L, -2, cstr!("ondown"));
    lua_pushnil(L);
    lua_setfield(L, -2, cstr!("onup"));
    lua_pushnil(L);
    lua_setfield(L, -2, cstr!("ondone"));

    lua_pop(L, 1);

    let key = (&gldraw_lua_key) as *const i32 as i32 + gldraw_lua_key;
    lua_pushlightuserdata(L, key as *mut c_void);

    let script = script.unwrap_or(DEFAULT_SCRIPT);
    if !runstring(L, script, cstr!("interpolator script"), Sandboxed(GLDRAW_LUA_SANDBOX)) {
        let err = format!("script failed to load: {}", err_to_str(L));
        lua_pop(L, 1);
        return log_err(err);
    }

    push_sandbox(L);
    lua_getfield(L, -1, cstr!("main"));
    if !lua_isfunction(L, -1) {
        lua_pop(L, 3);
        return log_err("no main function defined :(".into_string());
    }
    luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE as i32|LUAJIT_MODE_ON as i32);
    lua_pop(L, 1);

    // make values defined in script available to lua_runner
    lua_setglobal(L, cstr!("callbacks"));

    // FIXME compile runner once
    if !runstring(L, LUA_RUNNER, cstr!("built-in lua_runner script"), Unsandboxed) {
        let err = format!("lua runner failed to load: {}\n This should never happen!", err_to_str(L));
        lua_pop(L, 1);
        return log_err(err);
    }

    lua_getglobal(L, cstr!("runmain"));
    if !lua_isfunction(L, -1) {
        lua_pop(L, 2);
        return log_err("runmain not defined.\nThis should never happen!".into_string());
    }
    luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE as i32|LUAJIT_MODE_ON as i32);

    push_sandbox(L);
    lua_pushlightuserdata(L, GLDRAW_LUA_STOPFNS);
    lua_gettable(L, LUA_REGISTRYINDEX);
    // stack holds sandbox -- stopfns
    lua_pushlightuserdata(L, key as *mut c_void);
    // stack holds sandbox -- stopfns -- stopidx
    lua_getfield(L, -3, cstr!("ondone"));
    // stack holds sandbox -- stopfns -- stopidx -- ondone
    if !lua_isfunction(L, -1) {
        lua_pop(L, 6);
        return log_err("ondone not defined.\nThis should never happen!".into_string());
    }
    lua_settable(L, -3);
    // stack holds sandbox -- stopfns
    lua_pop(L, 2);

    lua_settable(L, LUA_REGISTRYINDEX);
    gldraw_lua_key += 1;
    logi!("created script for {}", script);
    Ok(key)
}

pub unsafe fn finish_lua_script(output: &mut LuaCallbackType, script: &::luascript::LuaScript) -> GLResult<()> {
    let L = get_lua().unwrap();
    lua_pushlightuserdata(L, GLDRAW_LUA_STOPFNS);
    lua_gettable(L, LUA_REGISTRYINDEX);
    // stack is stopfns
    script.push_self();
    lua_gettable(L, -2);
    // stack is stopfns -- stopfn
    lua_pushlightuserdata(L, output as *mut LuaCallbackType as *mut c_void);
    let result = match lua_pcall(L, 1, 0, 0) {
        0 => Ok(()),
        _ => log_err(format!("ondone() script failed to run: {}", err_to_str(L))),
    };
    lua_pop(L, 1); // remove stopfns
    result
}

pub unsafe fn destroy_lua_script(key: i32) {
    let L = get_lua().unwrap();
    lua_pushlightuserdata(L, key as *mut c_void);
    lua_pushnil(L);
    lua_settable(L, LUA_REGISTRYINDEX);
}

fn log_err<T>(message: String) -> GLResult<T> {
    loge(message.as_slice());
    Err(message)
}

#[inline]
pub unsafe fn push_lua_script(key: i32) {
    let L = get_lua().unwrap();
    lua_pushlightuserdata(L, key as *mut c_void);
    lua_gettable(L, LUA_REGISTRYINDEX);
}

pub unsafe fn do_interpolate_lua(script: &::luascript::LuaScript, dimensions: (i32, i32), output: &mut LuaCallbackType) -> GLResult<()> {
    logi!("prepping {}", script);
    let L = output.lua;
    script.push_self();

    let (x, y) = dimensions;
    lua_pushnumber(L, x as f64);
    lua_pushnumber(L, y as f64);
    lua_pushlightuserdata(L, output as *mut LuaCallbackType as *mut c_void);

    match lua_pcall(L, 3, 0, 0) {
        0 => Ok(()),
        _ => log_err(format!("script failed to run: {}", err_to_str(L))),
    }
}
<commit_msg>fix getting lua ondone() fn; indent to represent lua stack<commit_after>#![allow(non_snake_case)]
use core::prelude::*;
use core::{mem, ptr, raw};
use core::str::raw::c_str_to_static_slice;
use collections::str::StrAllocating;
use collections::string::String;
use libc::{c_char, c_void, size_t};

use lua::lib::raw::*;
use lua::aux::raw::*;
use lua::raw::*;
use luajit::*;
use luajit_constants::*;

use glcommon::GLResult;
use log::{logi, loge};

use lua_callbacks::LuaCallbackType;

static mut GLDRAW_LUA_SANDBOX: *mut c_void = 0 as *mut c_void;
static mut GLDRAW_LUA_STOPFNS: *mut c_void = 0 as *mut c_void;
static mut gldraw_lua_key: i32 = 0;
static LUA_FFI_SCRIPT: &'static str = include_str!("../includes/lua/ffi_loader.lua");
static LUA_RUNNER: &'static str = include_str!("../includes/lua/lua_runner.lua");
static DEFAULT_SCRIPT: &'static str = include_str!("../includes/lua/default_interpolator.lua");

static mut STATIC_LUA: Option<*mut lua_State> = None;

type ReaderState<'a> = (&'a str, bool);

enum SandboxMode {
    Sandboxed(*mut c_void),
    Unsandboxed,
}

#[allow(unused)]
extern "C" fn stringreader(L: *mut lua_State, data: *mut c_void, size: *mut size_t) -> *const c_char {
    unsafe {
        let state: &mut ReaderState = mem::transmute(data);
        let (ref chars, ref mut done) = *state;
        if *done {
            ptr::null()
        } else {
            *done = true;
            *size = chars.len() as u32;
            chars.as_ptr() as *const i8
        }
    }
}

unsafe fn err_to_str(L: *mut lua_State) -> String {
    let mut size: size_t = 0;
    let strptr = lua_tolstring(L, -1, &mut size);
    let luastr: &str = mem::transmute(raw::Slice { data: strptr, len: size as uint });
    let result = luastr.into_string();
    lua_pop(L, -1);
    result
}

unsafe fn runstring(L: *mut lua_State, s: &str, filename: *const i8, env: SandboxMode) -> bool {
    let mut state: ReaderState = (s, false);
    let stateref: *mut c_void = mem::transmute(&mut state);
    if 0 != lua_load(L, stringreader, stateref, filename) {
        false
        //Err(format!("script failed to load: {}", err_to_str(L)))
    } else {
        if let Sandboxed(key) = env {
            lua_pushlightuserdata(L, key);
            lua_gettable(L, LUA_REGISTRYINDEX);
            lua_setfenv(L, -2);
        }
        if 0 != lua_pcall(L, 0, MULTRET, 0) {
            false
            //Err(format!("script failed to run: {}", err_to_str(L)));
        } else {
            true
            //Ok(())
        }
    }
}

unsafe fn init_lua() -> GLResult<*mut lua_State> {
    GLDRAW_LUA_SANDBOX = &mut GLDRAW_LUA_SANDBOX as *mut *mut c_void as *mut c_void;
    GLDRAW_LUA_STOPFNS = &mut GLDRAW_LUA_STOPFNS as *mut *mut c_void as *mut c_void;

    let L = luaL_newstate();
    luaL_openlibs(L);

    luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE as i32|LUAJIT_MODE_ON as i32);

    if runstring(L, LUA_FFI_SCRIPT, cstr!("built-in ffi init script"), Unsandboxed) {
        logi!("ffi init script loaded");
        lua_pushlightuserdata(L, GLDRAW_LUA_SANDBOX);
        lua_getglobal(L, cstr!("sandboxed"));
        lua_settable(L, LUA_REGISTRYINDEX);

        lua_pushlightuserdata(L, GLDRAW_LUA_STOPFNS);
        lua_newtable(L);
        lua_settable(L, LUA_REGISTRYINDEX);
        Ok(L)
    } else {
        let err = format!("ffi init script failed to load: {}\nThis should never happen!", err_to_str(L));
        lua_close(L);
        log_err(err)
    }
}

unsafe fn get_lua() -> GLResult<*mut lua_State> {
    match STATIC_LUA {
        Some(x) => Ok(x),
        None => {
            STATIC_LUA = Some(try!(init_lua()));
            get_lua()
        }
    }
}

#[inline(always)]
pub unsafe fn get_existing_lua() -> Option<*mut lua_State> {
    STATIC_LUA
}

unsafe fn push_sandbox(L: *mut lua_State) {
    lua_pushlightuserdata(L, GLDRAW_LUA_SANDBOX);
    lua_gettable(L, LUA_REGISTRYINDEX);
}

unsafe fn clear_sandbox(L: *mut lua_State) {
    push_sandbox(L);
    lua_pushnil(L);
    lua_setfield(L, -2, cstr!("main"));
    lua_pushnil(L);
    lua_setfield(L, -2, cstr!("onframe"));
    lua_pushnil(L);
    lua_setfield(L, -2, cstr!("ondown"));
    lua_pushnil(L);
    lua_setfield(L, -2, cstr!("onup"));
    lua_pushnil(L);
    lua_setfield(L, -2, cstr!("ondone"));
    lua_pop(L, 1);
}

unsafe fn save_ondone(L: *mut lua_State, key: i32) -> GLResult<()> {
    logi!("saving ondone() method");
    push_sandbox(L); {
        lua_pushlightuserdata(L, GLDRAW_LUA_STOPFNS); {
            lua_gettable(L, LUA_REGISTRYINDEX);
            logi!("type of gldraw_lua_stopfns is {}", c_str_to_static_slice(lua_typename(L, lua_type(L, -1))));
            // stack holds sandbox -- stopfns
            lua_pushlightuserdata(L, key as *mut c_void); {
                // stack holds sandbox -- stopfns -- stopidx
                lua_getfield(L, -3, cstr!("ondone")); {
                    // stack holds sandbox -- stopfns -- stopidx -- ondone
                    if !lua_isfunction(L, -1) {
                        lua_pop(L, 4);
                        return log_err("ondone not defined.\nThis should never happen!".into_string());
                    }
                    logi!("saving ondone method to 0x{:x} in gldraw_lua_stopfns", key);
                    lua_settable(L, -3);
                }
            }
            // stack holds sandbox -- stopfns
            lua_pop(L, 2);
            Ok(())
        }
    }
}

pub unsafe fn load_lua_script(script: Option<&str>) -> GLResult<i32> {
    let L = try!(get_lua());
    logi!("got lua");

    clear_sandbox(L);

    let key = (&gldraw_lua_key) as *const i32 as i32 + gldraw_lua_key;
    let script = script.unwrap_or(DEFAULT_SCRIPT);
    lua_pushlightuserdata(L, key as *mut c_void); {

        if !runstring(L, script, cstr!("interpolator script"), Sandboxed(GLDRAW_LUA_SANDBOX)) {
            let err = format!("script failed to load: {}", err_to_str(L));
            lua_pop(L, 1);
            return log_err(err);
        }

        push_sandbox(L); {
            lua_getfield(L, -1, cstr!("main")); {
                if !lua_isfunction(L, -1) {
                    lua_pop(L, 3);
                    return log_err("no main function defined :(".into_string());
                }
                luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE as i32|LUAJIT_MODE_ON as i32);
                lua_pop(L, 1);
            }

            // make values defined in script available to lua_runner
            lua_setglobal(L, cstr!("callbacks"));
        }

        // FIXME compile runner once
        if !runstring(L, LUA_RUNNER, cstr!("built-in lua_runner script"), Unsandboxed) {
            let err = format!("lua runner failed to load: {}\n This should never happen!", err_to_str(L));
            lua_pop(L, 1);
            return log_err(err);
        }

        lua_getglobal(L, cstr!("runmain")); {
            if !lua_isfunction(L, -1) {
                lua_pop(L, 2);
                return log_err("runmain not defined.\nThis should never happen!".into_string());
            }
            luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE as i32|LUAJIT_MODE_ON as i32);

            if let Err(msg) = save_ondone(L, key) {
                lua_pop(L, 2);
                return Err(msg);
            }

            lua_settable(L, LUA_REGISTRYINDEX);
        }
    }

    gldraw_lua_key += 1;
    logi!("created script for {}", script);
    Ok(key)
}

pub unsafe fn finish_lua_script(output: &mut LuaCallbackType, script: &::luascript::LuaScript) -> GLResult<()> {
    let L = get_lua().unwrap();
    lua_pushlightuserdata(L, GLDRAW_LUA_STOPFNS);
    lua_gettable(L, LUA_REGISTRYINDEX);
    logi!("type of gldraw_lua_stopfns is {}", c_str_to_static_slice(lua_typename(L, lua_type(L, -1))));

    // stack is stopfns
    script.push_self();
    lua_gettable(L, -2);
    logi!("type of stopfn for {} is {}", script, c_str_to_static_slice(lua_typename(L, lua_type(L, -1))));
    // stack is stopfns -- stopfn
    lua_pushlightuserdata(L, output as *mut LuaCallbackType as *mut c_void);
    let result = match lua_pcall(L, 1, 0, 0) {
        0 => Ok(()),
        _ => log_err(format!("ondone() script failed to run: {}", err_to_str(L))),
    };
    lua_pop(L, 1); // remove stopfns
    result
}

pub unsafe fn destroy_lua_script(key: i32) {
    let L = get_lua().unwrap();
    lua_pushlightuserdata(L, key as *mut c_void);
    lua_pushnil(L);
    lua_settable(L, LUA_REGISTRYINDEX);
}

fn log_err<T>(message: String) -> GLResult<T> {
    loge(message.as_slice());
    Err(message)
}

#[inline]
pub unsafe fn push_lua_script(key: i32) {
    let L = get_lua().unwrap();
    lua_pushlightuserdata(L, key as *mut c_void);
}

pub unsafe fn do_interpolate_lua(script: &::luascript::LuaScript, dimensions: (i32, i32), output: &mut LuaCallbackType) -> GLResult<()> {
    logi!("prepping {}", script);
    let L = output.lua;
    script.push_self();
    lua_gettable(L, LUA_REGISTRYINDEX);

    let (x, y) = dimensions;
    lua_pushnumber(L, x as f64);
    lua_pushnumber(L, y as f64);
    lua_pushlightuserdata(L, output as *mut LuaCallbackType as *mut c_void);

    match lua_pcall(L, 3, 0, 0) {
        0 => Ok(()),
        _ => log_err(format!("script failed to run: {}", err_to_str(L))),
    }
}
<|endoftext|>


<commit_before>use dirs;
use libc;
use logger::{self, Level};
use std::fs::{self, DirBuilder};
use std::io::{self, Read};
use std::os::unix::fs::DirBuilderExt;
use std::path::{Path, PathBuf};

pub const NEWSBOAT_SUBDIR_XDG: &str = "newsboat";
pub const NEWSBOAT_CONFIG_SUBDIR: &str = ".newsboat";
pub const NEWSBEUTER_SUBDIR_XDG: &str = "newsbeuter";
pub const NEWSBEUTER_CONFIG_SUBDIR: &str = ".newsbeuter";
pub const LOCK_SUFFIX: &str = ".lock";

#[derive(Debug)]
pub struct ConfigPaths {
    m_env_home: PathBuf,
    m_error_message: String,

    m_data_dir: PathBuf,
    m_config_dir: PathBuf,

    m_url_file: String,
    m_cache_file: String,
    m_config_file: String,
    m_lock_file: String,
    m_queue_file: String,
    m_search_file: String,
    m_cmdline_file: String,

    m_silent: bool,
    m_using_nonstandard_configs: bool,
}

impl ConfigPaths {
    pub fn new() -> ConfigPaths {
        let mut config_paths = ConfigPaths {
            m_env_home: PathBuf::new(),
            m_error_message: String::new(),

            m_data_dir: PathBuf::new(),
            m_config_dir: PathBuf::new(),

            m_url_file: String::from("urls"),
            m_cache_file: String::from("cache.db"),
            m_config_file: String::from("config"),
            m_lock_file: String::new(),
            m_queue_file: String::from("queue"),
            m_search_file: String::new(),
            m_cmdline_file: String::new(),

            m_silent: false,
            m_using_nonstandard_configs: false,
        };

        let m_env_home = dirs::home_dir();
        if m_env_home.is_none() {
            let uid = unsafe { libc::getuid() };

            // TODO: i18n
            config_paths.m_error_message = format!("Fatal error: couldn't determine home directory!\nPlease set the HOME environment variable or add a valid user for UID {}!", uid);

            return config_paths;
        }

        // hitting this branch means we found a home directory
        // we can now safely call unwrap on all other functions in the dirs crate

        config_paths.m_env_home = m_env_home.unwrap();
        config_paths.find_dirs();
        config_paths
    }

    fn migrate_data_from_newsbeuter_xdg(&mut self) -> bool {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let xdg_config_dir = dirs::config_dir().unwrap();
        let xdg_data_dir = dirs::data_dir().unwrap();

        let newsbeuter_config_dir = xdg_config_dir.join(NEWSBEUTER_SUBDIR_XDG);
        let newsbeuter_data_dir = xdg_data_dir.join(NEWSBEUTER_SUBDIR_XDG);

        let newsboat_config_dir = xdg_config_dir.join(NEWSBOAT_SUBDIR_XDG);
        let newsboat_data_dir = xdg_data_dir.join(NEWSBOAT_SUBDIR_XDG);

        if !newsbeuter_config_dir.is_dir() {
            return false;
        }

        fn exists(path: &Path) -> bool {
            let exists = path.exists();
            if exists {
                log!(
                    Level::Debug,
                    "{:?} already exists, aborting XDG migration.",
                    path
                );
            }
            return exists;
        }

        if exists(&newsboat_config_dir) {
            return false;
        }

        if exists(&newsboat_data_dir) {
            return false;
        }

        self.m_config_dir = newsboat_config_dir;
        self.m_data_dir = newsboat_data_dir;

        if !self.m_silent {
            eprintln!("Migrating configs and data from Newsbeuter's XDG dirs...");
        }

        if !try_mkdir(&self.m_config_dir) {
            return false;
        }

        if !try_mkdir(&self.m_data_dir) {
            return false;
        }

        // in config
        migrate_file(&newsbeuter_config_dir, &self.m_config_dir, "urls").is_ok();
        migrate_file(&newsbeuter_config_dir, &self.m_config_dir, "config").is_ok();

        // in data
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "cache.db").is_ok();
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "queue").is_ok();
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "history.search").is_ok();
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "history.cmdline").is_ok();

        return true;
    }

    fn migrate_data_from_newsbeuter_simple(&self) -> bool {
        let newsbeuter_dir = self.m_env_home.join(NEWSBEUTER_CONFIG_SUBDIR);

        if !newsbeuter_dir.is_dir() {
            return false;
        }

        let newsboat_dir = self.m_env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        if newsboat_dir.exists() {
            log!(
                Level::Debug,
                "{:?} already exists, aborting migration.",
                newsboat_dir
            );
            return false;
        }

        if !self.m_silent {
            eprintln!("Migrating configs and data from Newsbeuter's dotdir...");
        }

        match mkdir(&newsboat_dir, 0o700) {
            Ok(_) => (),
            Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => (),
            Err(err) => {
                if !self.m_silent {
                    eprintln!(
                        "Aborting migration because mkdir on {:?} failed: {}",
                        newsboat_dir, err
                    );
                }
                return false;
            }
        };

        migrate_file(&newsbeuter_dir, &newsboat_dir, "urls").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "cache.db").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "config").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "queue").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "history.search").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "history.cmdline").ok();

        return true;
    }

    fn migrate_data_from_newsbeuter(&mut self) {
        let mut migrated = self.migrate_data_from_newsbeuter_xdg();

        if migrated {
            // Re-running to pick up XDG dirs
            self.m_url_file = "urls".into();
            self.m_cache_file = "cache.db".into();
            self.m_config_file = "config".into();
            self.m_queue_file = "queue".into();
            self.find_dirs();
        } else {
            migrated = self.migrate_data_from_newsbeuter_simple();
        }

        if migrated {
            eprintln!("\nPlease check the results and press Enter to continue.");
            // only wait for enter, ignore actual input
            let mut buf = [0; 16];
            io::stdin().read(&mut buf).ok();
        }
    }

    fn create_dirs(&self) -> bool {
        return try_mkdir(&self.m_config_dir) && try_mkdir(&self.m_data_dir);
    }

    fn find_dirs(&mut self) {
        self.m_config_dir = self.m_env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        self.m_data_dir = self.m_config_dir.clone();

        // Will change config_dir and data_dir to point to XDG if XDG
        // directories are available.
        self.find_dirs_xdg();

        // in config
        self.m_url_file = self
            .m_config_dir
            .join(&self.m_url_file)
            .to_string_lossy()
            .into();
        self.m_config_file = self
            .m_config_dir
            .join(&self.m_config_file)
            .to_string_lossy()
            .into();

        // in data
        self.m_cache_file = self
            .m_data_dir
            .join(&self.m_cache_file)
            .to_string_lossy()
            .into();
        self.m_lock_file = self.m_cache_file.clone() + LOCK_SUFFIX;
        self.m_queue_file = self
            .m_data_dir
            .join(&self.m_queue_file)
            .to_string_lossy()
            .into();
        self.m_search_file = self
            .m_data_dir
            .join("history.search")
            .to_string_lossy()
            .into();
        self.m_cmdline_file = self
            .m_data_dir
            .join("history.cmdline")
            .to_string_lossy()
            .into();
    }

    fn find_dirs_xdg(&mut self) {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let config_dir = dirs::config_dir().unwrap().join(NEWSBOAT_SUBDIR_XDG);
        let data_dir = dirs::data_dir().unwrap().join(NEWSBOAT_SUBDIR_XDG);

        if !config_dir.is_dir() {
            return;
        }

        /* Invariant: config dir exists.
         *
         * At this point, we're confident we'll be using XDG. We don't check if
         * data dir exists, because if it doesn't we'll create it. */

        self.m_config_dir = config_dir;
        self.m_data_dir = data_dir;
    }

    /// \brief Indicates if the object can be used.
    ///
    /// If this method returned `false`, the cause for initialization
    /// failure can be found using `error_message()`.
    pub fn initialized(&self) -> bool {
        !self.m_env_home.as_os_str().is_empty()
    }

    /// Returns explanation why initialization failed.
    ///
    /// \note You shouldn't call this unless `initialized()` returns
    /// `false`.
    pub fn error_message(&self) -> &str {
        &self.m_error_message
    }

    // TODO
    /*
    /// Initializes paths to config, cache etc. from CLI arguments.
    pub fn process_args(const CliArgsParser& args) {
        unimplemented!()
    }
    */

    /// Ensures all directories exist (migrating them from Newsbeuter if
    /// possible).
    pub fn setup_dirs(&mut self) -> bool {
        if !self.m_using_nonstandard_configs && !Path::new(&self.m_url_file).exists() {
            self.migrate_data_from_newsbeuter();
        }

        self.create_dirs()
    }

    /// Path to the URLs file.
    pub fn url_file(&self) -> String {
        self.m_url_file.clone()
    }

    /// Path to the cache file.
    pub fn cache_file(&self) -> String {
        self.m_cache_file.clone()
    }

    /// Sets path to the cache file.
    // FIXME: this is actually a kludge that lets Controller change the path
    // midway. That logic should be moved into ConfigPaths, and this method
    // removed.
    pub fn set_cache_file(&mut self, path: String) {
        self.m_cache_file = path.clone();
        self.m_lock_file = path + LOCK_SUFFIX;
    }

    /// Path to the config file.
    pub fn config_file(&self) -> String {
        self.m_config_file.clone()
    }

    /// Path to the lock file.
    ///
    /// \note This changes when path to config file changes.
    pub fn lock_file(&self) -> String {
        self.m_lock_file.clone()
    }

    /// \brief Path to the queue file.
    ///
    /// Queue file stores enqueued podcasts. It's written by Newsboat, and
    /// read by Podboat.
    pub fn queue_file(&self) -> String {
        self.m_queue_file.clone()
    }

    /// Path to the file with previous search queries.
    pub fn search_file(&self) -> String {
        self.m_search_file.clone()
    }

    /// Path to the file with command-line history.
    pub fn cmdline_file(&self) -> String {
        self.m_cmdline_file.clone()
    }
}

fn try_mkdir<R: AsRef<Path>>(path: R) -> bool {
    DirBuilder::new()
        .recursive(true)
        .mode(0o700)
        .create(path.as_ref())
        .is_ok()
}

fn mkdir<R: AsRef<Path>>(path: R, mode: u32) -> io::Result<()> {
    DirBuilder::new().mode(mode).create(path.as_ref())
}

fn migrate_file<R: AsRef<Path>>(newsbeuter_dir: R, newsboat_dir: R, file: &str) -> io::Result<()> {
    let input_filepath = newsbeuter_dir.as_ref().join(file);
    let output_filepath = newsboat_dir.as_ref().join(file);
    eprintln!("{:?} -> {:?}", input_filepath, output_filepath);
    fs::copy(input_filepath, output_filepath).map(|_| ())
}
<commit_msg>Drop Doxygen commands from docs<commit_after>use dirs;
use libc;
use logger::{self, Level};
use std::fs::{self, DirBuilder};
use std::io::{self, Read};
use std::os::unix::fs::DirBuilderExt;
use std::path::{Path, PathBuf};

pub const NEWSBOAT_SUBDIR_XDG: &str = "newsboat";
pub const NEWSBOAT_CONFIG_SUBDIR: &str = ".newsboat";
pub const NEWSBEUTER_SUBDIR_XDG: &str = "newsbeuter";
pub const NEWSBEUTER_CONFIG_SUBDIR: &str = ".newsbeuter";
pub const LOCK_SUFFIX: &str = ".lock";

#[derive(Debug)]
pub struct ConfigPaths {
    m_env_home: PathBuf,
    m_error_message: String,

    m_data_dir: PathBuf,
    m_config_dir: PathBuf,

    m_url_file: String,
    m_cache_file: String,
    m_config_file: String,
    m_lock_file: String,
    m_queue_file: String,
    m_search_file: String,
    m_cmdline_file: String,

    m_silent: bool,
    m_using_nonstandard_configs: bool,
}

impl ConfigPaths {
    pub fn new() -> ConfigPaths {
        let mut config_paths = ConfigPaths {
            m_env_home: PathBuf::new(),
            m_error_message: String::new(),

            m_data_dir: PathBuf::new(),
            m_config_dir: PathBuf::new(),

            m_url_file: String::from("urls"),
            m_cache_file: String::from("cache.db"),
            m_config_file: String::from("config"),
            m_lock_file: String::new(),
            m_queue_file: String::from("queue"),
            m_search_file: String::new(),
            m_cmdline_file: String::new(),

            m_silent: false,
            m_using_nonstandard_configs: false,
        };

        let m_env_home = dirs::home_dir();
        if m_env_home.is_none() {
            let uid = unsafe { libc::getuid() };

            // TODO: i18n
            config_paths.m_error_message = format!("Fatal error: couldn't determine home directory!\nPlease set the HOME environment variable or add a valid user for UID {}!", uid);

            return config_paths;
        }

        // hitting this branch means we found a home directory
        // we can now safely call unwrap on all other functions in the dirs crate

        config_paths.m_env_home = m_env_home.unwrap();
        config_paths.find_dirs();
        config_paths
    }

    fn migrate_data_from_newsbeuter_xdg(&mut self) -> bool {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let xdg_config_dir = dirs::config_dir().unwrap();
        let xdg_data_dir = dirs::data_dir().unwrap();

        let newsbeuter_config_dir = xdg_config_dir.join(NEWSBEUTER_SUBDIR_XDG);
        let newsbeuter_data_dir = xdg_data_dir.join(NEWSBEUTER_SUBDIR_XDG);

        let newsboat_config_dir = xdg_config_dir.join(NEWSBOAT_SUBDIR_XDG);
        let newsboat_data_dir = xdg_data_dir.join(NEWSBOAT_SUBDIR_XDG);

        if !newsbeuter_config_dir.is_dir() {
            return false;
        }

        fn exists(path: &Path) -> bool {
            let exists = path.exists();
            if exists {
                log!(
                    Level::Debug,
                    "{:?} already exists, aborting XDG migration.",
                    path
                );
            }
            return exists;
        }

        if exists(&newsboat_config_dir) {
            return false;
        }

        if exists(&newsboat_data_dir) {
            return false;
        }

        self.m_config_dir = newsboat_config_dir;
        self.m_data_dir = newsboat_data_dir;

        if !self.m_silent {
            eprintln!("Migrating configs and data from Newsbeuter's XDG dirs...");
        }

        if !try_mkdir(&self.m_config_dir) {
            return false;
        }

        if !try_mkdir(&self.m_data_dir) {
            return false;
        }

        // in config
        migrate_file(&newsbeuter_config_dir, &self.m_config_dir, "urls").is_ok();
        migrate_file(&newsbeuter_config_dir, &self.m_config_dir, "config").is_ok();

        // in data
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "cache.db").is_ok();
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "queue").is_ok();
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "history.search").is_ok();
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "history.cmdline").is_ok();

        return true;
    }

    fn migrate_data_from_newsbeuter_simple(&self) -> bool {
        let newsbeuter_dir = self.m_env_home.join(NEWSBEUTER_CONFIG_SUBDIR);

        if !newsbeuter_dir.is_dir() {
            return false;
        }

        let newsboat_dir = self.m_env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        if newsboat_dir.exists() {
            log!(
                Level::Debug,
                "{:?} already exists, aborting migration.",
                newsboat_dir
            );
            return false;
        }

        if !self.m_silent {
            eprintln!("Migrating configs and data from Newsbeuter's dotdir...");
        }

        match mkdir(&newsboat_dir, 0o700) {
            Ok(_) => (),
            Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => (),
            Err(err) => {
                if !self.m_silent {
                    eprintln!(
                        "Aborting migration because mkdir on {:?} failed: {}",
                        newsboat_dir, err
                    );
                }
                return false;
            }
        };

        migrate_file(&newsbeuter_dir, &newsboat_dir, "urls").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "cache.db").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "config").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "queue").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "history.search").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "history.cmdline").ok();

        return true;
    }

    fn migrate_data_from_newsbeuter(&mut self) {
        let mut migrated = self.migrate_data_from_newsbeuter_xdg();

        if migrated {
            // Re-running to pick up XDG dirs
            self.m_url_file = "urls".into();
            self.m_cache_file = "cache.db".into();
            self.m_config_file = "config".into();
            self.m_queue_file = "queue".into();
            self.find_dirs();
        } else {
            migrated = self.migrate_data_from_newsbeuter_simple();
        }

        if migrated {
            eprintln!("\nPlease check the results and press Enter to continue.");
            // only wait for enter, ignore actual input
            let mut buf = [0; 16];
            io::stdin().read(&mut buf).ok();
        }
    }

    fn create_dirs(&self) -> bool {
        return try_mkdir(&self.m_config_dir) && try_mkdir(&self.m_data_dir);
    }

    fn find_dirs(&mut self) {
        self.m_config_dir = self.m_env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        self.m_data_dir = self.m_config_dir.clone();

        // Will change config_dir and data_dir to point to XDG if XDG
        // directories are available.
        self.find_dirs_xdg();

        // in config
        self.m_url_file = self
            .m_config_dir
            .join(&self.m_url_file)
            .to_string_lossy()
            .into();
        self.m_config_file = self
            .m_config_dir
            .join(&self.m_config_file)
            .to_string_lossy()
            .into();

        // in data
        self.m_cache_file = self
            .m_data_dir
            .join(&self.m_cache_file)
            .to_string_lossy()
            .into();
        self.m_lock_file = self.m_cache_file.clone() + LOCK_SUFFIX;
        self.m_queue_file = self
            .m_data_dir
            .join(&self.m_queue_file)
            .to_string_lossy()
            .into();
        self.m_search_file = self
            .m_data_dir
            .join("history.search")
            .to_string_lossy()
            .into();
        self.m_cmdline_file = self
            .m_data_dir
            .join("history.cmdline")
            .to_string_lossy()
            .into();
    }

    fn find_dirs_xdg(&mut self) {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let config_dir = dirs::config_dir().unwrap().join(NEWSBOAT_SUBDIR_XDG);
        let data_dir = dirs::data_dir().unwrap().join(NEWSBOAT_SUBDIR_XDG);

        if !config_dir.is_dir() {
            return;
        }

        /* Invariant: config dir exists.
         *
         * At this point, we're confident we'll be using XDG. We don't check if
         * data dir exists, because if it doesn't we'll create it. */

        self.m_config_dir = config_dir;
        self.m_data_dir = data_dir;
    }

    /// Indicates if the object can be used.
    ///
    /// If this method returned `false`, the cause for initialization failure can be found using
    /// `error_message()`.
    pub fn initialized(&self) -> bool {
        !self.m_env_home.as_os_str().is_empty()
    }

    /// Returns explanation why initialization failed.
    ///
    /// \note You shouldn't call this unless `initialized()` returns `false`.
    pub fn error_message(&self) -> &str {
        &self.m_error_message
    }

    // TODO
    /*
    /// Initializes paths to config, cache etc. from CLI arguments.
    pub fn process_args(const CliArgsParser& args) {
        unimplemented!()
    }
    */

    /// Ensures all directories exist (migrating them from Newsbeuter if possible).
    pub fn setup_dirs(&mut self) -> bool {
        if !self.m_using_nonstandard_configs && !Path::new(&self.m_url_file).exists() {
            self.migrate_data_from_newsbeuter();
        }

        self.create_dirs()
    }

    /// Path to the URLs file.
    pub fn url_file(&self) -> String {
        self.m_url_file.clone()
    }

    /// Path to the cache file.
    pub fn cache_file(&self) -> String {
        self.m_cache_file.clone()
    }

    /// Sets path to the cache file.
    // FIXME: this is actually a kludge that lets Controller change the path
    // midway. That logic should be moved into ConfigPaths, and this method
    // removed.
    pub fn set_cache_file(&mut self, path: String) {
        self.m_cache_file = path.clone();
        self.m_lock_file = path + LOCK_SUFFIX;
    }

    /// Path to the config file.
    pub fn config_file(&self) -> String {
        self.m_config_file.clone()
    }

    /// Path to the lock file.
    ///
    /// \note This changes when path to config file changes.
    pub fn lock_file(&self) -> String {
        self.m_lock_file.clone()
    }

    /// \brief Path to the queue file.
    ///
    /// Queue file stores enqueued podcasts. It's written by Newsboat, and read by Podboat.
    pub fn queue_file(&self) -> String {
        self.m_queue_file.clone()
    }

    /// Path to the file with previous search queries.
    pub fn search_file(&self) -> String {
        self.m_search_file.clone()
    }

    /// Path to the file with command-line history.
    pub fn cmdline_file(&self) -> String {
        self.m_cmdline_file.clone()
    }
}

fn try_mkdir<R: AsRef<Path>>(path: R) -> bool {
    DirBuilder::new()
        .recursive(true)
        .mode(0o700)
        .create(path.as_ref())
        .is_ok()
}

fn mkdir<R: AsRef<Path>>(path: R, mode: u32) -> io::Result<()> {
    DirBuilder::new().mode(mode).create(path.as_ref())
}

fn migrate_file<R: AsRef<Path>>(newsbeuter_dir: R, newsboat_dir: R, file: &str) -> io::Result<()> {
    let input_filepath = newsbeuter_dir.as_ref().join(file);
    let output_filepath = newsboat_dir.as_ref().join(file);
    eprintln!("{:?} -> {:?}", input_filepath, output_filepath);
    fs::copy(input_filepath, output_filepath).map(|_| ())
}
<|endoftext|>


<commit_before>use std::mem;
use std::any::{Any, TypeId};
use std::ops::Deref;
use std::sync::Arc;

use na;

// Repr.
use shape::{CompositeShape, SupportMap};
// Queries.
use bounding_volume::{AABB, BoundingSphere};
use query::{RayCast, PointQuery};
use math::Point;

/// Trait implemented by all shapes supported by ncollide.
///
/// This allows dynamic inspection of the shape capabilities.
pub trait Shape<P: Point, M> : Send + Sync + Any + GetTypeId {
    /// The AABB of `self`.
    #[inline]
    fn aabb(&self, m: &M) -> AABB<P>;

    /// The bounding sphere of `self`.
    #[inline]
    fn bounding_sphere(&self, m: &M) -> BoundingSphere<P> {
        let aabb = self.aabb(m);

        BoundingSphere::new(aabb.center(), na::norm_squared(&aabb.half_extents()))
    }

    /// The `RayCast` implementation of `self`.
    #[inline]
    fn as_ray_cast(&self) -> Option<&RayCast<P, M>> {
        None
    }

    /// The `PointQuery` implementation of `self`.
    #[inline]
    fn as_point_query(&self) -> Option<&PointQuery<P, M>> {
        None
    }

    /// The support mapping of `self` if applicable.
    #[inline]
    fn as_support_map(&self) -> Option<&SupportMap<P, M>> {
        None
    }

    /// The composite shape representation of `self` if applicable.
    #[inline]
    fn as_composite_shape(&self) -> Option<&CompositeShape<P, M>> {
        None
    }

    /// Whether `self` uses a supportmapping-based representation.
    #[inline]
    fn is_support_map(&self) -> bool {
        self.as_support_map().is_some()
    }

    /// Whether `self` uses a composite shape-based representation.
    #[inline]
    fn is_composite_shape(&self) -> bool {
        self.as_composite_shape().is_some()
    }
}

// Define our own because it is unstable.
/// Raw representation of a trait-object.
#[repr(C)]
#[derive(Clone, Copy)]
struct TraitObject {
    /// Raw pointer to the trait object data.
    pub data:   *mut (),
    /// Raw pointer to the trait object virtual table.
    pub vtable: *mut ()
}

/// Trait for casting shapes to its exact represetation.
impl<P: Point, M: 'static> Shape<P, M> {
    /// Tests if this shape has a specific type `T`.
    #[inline]
    pub fn is_shape<T: Shape<P, M>>(&self) -> bool {
        self.type_id() == TypeId::of::<T>()
    }

    /// Performs the cast.
    #[inline]
    pub fn as_shape<T: Shape<P, M>>(&self) -> Option<&T> {
        if self.is_shape::<T>() {
            unsafe {
                let to: TraitObject = mem::transmute(self);
                mem::transmute(to.data)
            }
        }
        else {
            None
        }
    }
}

/// A shared immutable handle to an abstract shape.
#[derive(Clone)]
pub struct ShapeHandle<P: Point, M> {
    handle: Arc<Box<Shape<P, M>>>
}

impl<P: Point, M> ShapeHandle<P, M> {
    /// Creates a sharable shape handle from a shape.
    #[inline]
    pub fn new<S: Shape<P, M>>(shape: S) -> ShapeHandle<P, M> {
        ShapeHandle {
            handle: Arc::new(Box::new(shape) as Box<Shape<P, M>>)
        }
    }
}

impl<P: Point, M> AsRef<Shape<P, M>> for ShapeHandle<P, M> {
    #[inline]
    fn as_ref(&self) -> &Shape<P, M> {
        self.deref()
    }
}

impl<P: Point, M> Deref for ShapeHandle<P, M> {
    type Target = Shape<P, M>;

    #[inline]
    fn deref(&self) -> &Shape<P, M> {
        &**self.handle
    }
}

/// Trait to retrieve the `TypeId` of a shape.
///
/// This exists only because `Any::get_type_id()` is unstable.
pub unsafe trait GetTypeId {
    /// Gets the dynamic type identifier of this shape.
    #[inline]
    fn type_id(&self) -> TypeId;
}

unsafe impl<T: Any> GetTypeId for T {
    #[inline]
    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}
<commit_msg>Reduce indirection in ShapeHandle internals<commit_after>use std::mem;
use std::any::{Any, TypeId};
use std::ops::Deref;
use std::sync::Arc;

use na;

// Repr.
use shape::{CompositeShape, SupportMap};
// Queries.
use bounding_volume::{AABB, BoundingSphere};
use query::{RayCast, PointQuery};
use math::Point;

/// Trait implemented by all shapes supported by ncollide.
///
/// This allows dynamic inspection of the shape capabilities.
pub trait Shape<P: Point, M> : Send + Sync + Any + GetTypeId {
    /// The AABB of `self`.
    #[inline]
    fn aabb(&self, m: &M) -> AABB<P>;

    /// The bounding sphere of `self`.
    #[inline]
    fn bounding_sphere(&self, m: &M) -> BoundingSphere<P> {
        let aabb = self.aabb(m);

        BoundingSphere::new(aabb.center(), na::norm_squared(&aabb.half_extents()))
    }

    /// The `RayCast` implementation of `self`.
    #[inline]
    fn as_ray_cast(&self) -> Option<&RayCast<P, M>> {
        None
    }

    /// The `PointQuery` implementation of `self`.
    #[inline]
    fn as_point_query(&self) -> Option<&PointQuery<P, M>> {
        None
    }

    /// The support mapping of `self` if applicable.
    #[inline]
    fn as_support_map(&self) -> Option<&SupportMap<P, M>> {
        None
    }

    /// The composite shape representation of `self` if applicable.
    #[inline]
    fn as_composite_shape(&self) -> Option<&CompositeShape<P, M>> {
        None
    }

    /// Whether `self` uses a supportmapping-based representation.
    #[inline]
    fn is_support_map(&self) -> bool {
        self.as_support_map().is_some()
    }

    /// Whether `self` uses a composite shape-based representation.
    #[inline]
    fn is_composite_shape(&self) -> bool {
        self.as_composite_shape().is_some()
    }
}

// Define our own because it is unstable.
/// Raw representation of a trait-object.
#[repr(C)]
#[derive(Clone, Copy)]
struct TraitObject {
    /// Raw pointer to the trait object data.
    pub data:   *mut (),
    /// Raw pointer to the trait object virtual table.
    pub vtable: *mut ()
}

/// Trait for casting shapes to its exact represetation.
impl<P: Point, M: 'static> Shape<P, M> {
    /// Tests if this shape has a specific type `T`.
    #[inline]
    pub fn is_shape<T: Shape<P, M>>(&self) -> bool {
        self.type_id() == TypeId::of::<T>()
    }

    /// Performs the cast.
    #[inline]
    pub fn as_shape<T: Shape<P, M>>(&self) -> Option<&T> {
        if self.is_shape::<T>() {
            unsafe {
                let to: TraitObject = mem::transmute(self);
                mem::transmute(to.data)
            }
        }
        else {
            None
        }
    }
}

/// A shared immutable handle to an abstract shape.
#[derive(Clone)]
pub struct ShapeHandle<P: Point, M> {
    handle: Arc<Shape<P, M>>
}

impl<P: Point, M> ShapeHandle<P, M> {
    /// Creates a sharable shape handle from a shape.
    #[inline]
    pub fn new<S: Shape<P, M>>(shape: S) -> ShapeHandle<P, M> {
        ShapeHandle {
            handle: Arc::new(shape)
        }
    }
}

impl<P: Point, M> AsRef<Shape<P, M>> for ShapeHandle<P, M> {
    #[inline]
    fn as_ref(&self) -> &Shape<P, M> {
        self.deref()
    }
}

impl<P: Point, M> Deref for ShapeHandle<P, M> {
    type Target = Shape<P, M>;

    #[inline]
    fn deref(&self) -> &Shape<P, M> {
        self.handle.deref()
    }
}

/// Trait to retrieve the `TypeId` of a shape.
///
/// This exists only because `Any::get_type_id()` is unstable.
pub unsafe trait GetTypeId {
    /// Gets the dynamic type identifier of this shape.
    #[inline]
    fn type_id(&self) -> TypeId;
}

unsafe impl<T: Any> GetTypeId for T {
    #[inline]
    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}
<|endoftext|>


<commit_before>use buffer::{Lexeme, Position, Token};
use syntect::parsing::{ParseState, ScopeStack, ScopeStackOp, SyntaxDefinition};
use buffer::token::line_iterator::LineIterator;

pub struct TokenIterator<'a> {
    scopes: ScopeStack,
    parser: ParseState,
    lines: LineIterator<'a>,
    current_line: Option<&'a str>,
    current_position: Position,
    line_events: Vec<(usize, ScopeStackOp)>,
}

impl<'a> TokenIterator<'a> {
    pub fn new(data: &'a str, def: &SyntaxDefinition) -> TokenIterator<'a> {
        let mut token_iterator = TokenIterator{
            scopes: ScopeStack::new(),
            parser: ParseState::new(def),
            lines: LineIterator::new(data),
            current_line: None,
            current_position: Position{ line: 0, offset: 0 },
            line_events: Vec::new(),
        };

        // Preload the first line
        token_iterator.parse_next_line();

        token_iterator
    }

    fn next_token(&mut self) -> Option<Token<'a>> {
        // Try to fetch a token from the current line.
        if let Some(token) = self.build_next_token() {
            return Some(token)
        }

        // We're done with this line; on to the next.
        self.parse_next_line();
        if self.current_line.is_some() {
            Some(Token::Newline)
        } else {
            None
        }
    }

    fn build_next_token(&mut self) -> Option<Token<'a>> {
        let mut lexeme = None;

        if let Some(line) = self.current_line {
            while let Some((event_offset, scope_change)) = self.line_events.pop() {
                // We want to capture the full scope for a given token, so we
                // need to make sure we apply all of them and only capture it
                // once we've moved on to another token/offset.
                if event_offset > self.current_position.offset {
                    lexeme = Some(
                        Token::Lexeme(Lexeme{
                            value: &line[self.current_position.offset..event_offset],
                            scope: self.scopes.clone(),
                            position: self.current_position.clone(),
                        })
                    );
                    self.current_position.offset = event_offset;
                }

                // Apply the scope and keep a reference to it, so
                // that we can pair it with a token later on.
                self.scopes.apply(&scope_change);

                if lexeme.is_some() { return lexeme }
            }

            // We already have discrete variant for newlines,
            // so exclude them when considering content length.
            if let Some(end_of_line) = line.len().checked_sub(1) {
                if self.current_position.offset < end_of_line {
                    // The rest of the line hasn't triggered a scope
                    // change; categorize it with the last known scope.
                    lexeme = Some(
                        Token::Lexeme(Lexeme{
                            value: &line[self.current_position.offset..end_of_line],
                            scope: self.scopes.clone(),
                            position: self.current_position.clone(),
                        })
                    );
                }

            };
        }
        self.current_line = None;

        if lexeme.is_some() {
            lexeme
        } else {
            None
        }
    }

    fn parse_next_line(&mut self) {
        if let Some((line_number, line)) = self.lines.next() {
            // We reverse the line elements so that we can pop them off one at a
            // time, handling each event while allowing us to stop at any point.
            let mut line_events = self.parser.parse_line(line);
            line_events.reverse();
            self.line_events = line_events;

            // Keep a reference to the line so that we can create slices of it.
            self.current_line = Some(line);

            // Track our position, which we'll pass to generated tokens.
            self.current_position = Position{ line: line_number, offset: 0 };
        } else {
            self.current_line = None;
        }
    }
}

impl<'a> Iterator for TokenIterator<'a> {
    type Item = Token<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        self.next_token()
    }
}

#[cfg(test)]
mod tests {
    use super::TokenIterator;
    use buffer::{Lexeme, Position, Scope, Token};
    use syntect::parsing::{ScopeStack, SyntaxSet};

    #[test]
    fn token_iterator_returns_correct_tokens() {
        let syntax_set = SyntaxSet::load_defaults_newlines();
        let def = syntax_set.find_syntax_by_extension("rs");
        let iterator = TokenIterator::new("struct Buffer {\n// comment\n  data: String\n}garbage\n\n", def.unwrap());
        let mut scope_stack = ScopeStack::new();
        let mut expected_tokens = Vec::new();
        scope_stack.push(Scope::new("source.rust").unwrap());
        scope_stack.push(Scope::new("meta.struct.rust").unwrap());
        scope_stack.push(Scope::new("storage.type.struct.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "struct",
            scope: scope_stack.clone(),
            position: Position{ line: 0, offset: 0 }
        }));
        scope_stack.pop();
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: " ",
            scope: scope_stack.clone(),
            position: Position{ line: 0, offset: 6 }
        }));
        scope_stack.push(Scope::new("entity.name.struct.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "Buffer",
            scope: scope_stack.clone(),
            position: Position{ line: 0, offset: 7 }
        }));
        scope_stack.pop();
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: " ",
            scope: scope_stack.clone(),
            position: Position{ line: 0, offset: 13 }
        }));
        scope_stack.push(Scope::new("meta.block.rust").unwrap());
        scope_stack.push(Scope::new("punctuation.definition.block.begin.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "{",
            scope: scope_stack.clone(),
            position: Position{ line: 0, offset: 14 }
        }));
        expected_tokens.push(Token::Newline);
        scope_stack.pop();
        scope_stack.push(Scope::new("comment.line.double-slash.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "// comment",
            scope: scope_stack.clone(),
            position: Position{ line: 1, offset: 0 }
        }));
        expected_tokens.push(Token::Newline);
        scope_stack.pop();
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "  ",
            scope: scope_stack.clone(),
            position: Position{ line: 2, offset: 0 }
        }));
        scope_stack.push(Scope::new("variable.other.property.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "data",
            scope: scope_stack.clone(),
            position: Position{ line: 2, offset: 2 }
        }));
        scope_stack.pop();
        scope_stack.push(Scope::new("punctuation.separator.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: ":",
            scope: scope_stack.clone(),
            position: Position{ line: 2, offset: 6 }
        }));
        scope_stack.pop();
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: " String",
            scope: scope_stack.clone(),
            position: Position{ line: 2, offset: 7 }
        }));
        expected_tokens.push(Token::Newline);
        scope_stack.push(Scope::new("punctuation.definition.block.end.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "}",
            scope: scope_stack.clone(),
            position: Position{ line: 3, offset: 0 }
        }));
        scope_stack.pop();
        scope_stack.pop();
        scope_stack.pop();
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "garbage",
            scope: scope_stack.clone(),
            position: Position{ line: 3, offset: 1 }
        }));
        expected_tokens.push(Token::Newline);
        expected_tokens.push(Token::Newline);
        let actual_tokens: Vec<Token> = iterator.collect();
        for (index, token) in expected_tokens.into_iter().enumerate() {
            assert_eq!(token, actual_tokens[index]);
        }

        //assert_eq!(expected_tokens, actual_tokens);
    }

    #[test]
    fn token_iterator_handles_content_without_trailing_newline() {
        let syntax_set = SyntaxSet::load_defaults_newlines();
        let def = syntax_set.find_syntax_by_extension("rs");
        let iterator = TokenIterator::new("struct", def.unwrap());
        let mut expected_tokens = Vec::new();
        expected_tokens.push(
            Token::Lexeme(Lexeme{
                value: "struct",
                scope: ScopeStack::from_vec(vec![
                    Scope::new("source.rust").unwrap(),
                    Scope::new("storage.type.struct.rust").unwrap()
                ]),
                position: Position{ line: 0, offset: 0 }
            })
        );
        let actual_tokens: Vec<Token> = iterator.collect();
        for (index, token) in expected_tokens.into_iter().enumerate() {
            assert_eq!(token, actual_tokens[index]);
        }

        //assert_eq!(expected_tokens, actual_tokens);
    }
}
<commit_msg>Fix token iterator test imports<commit_after>use buffer::{Lexeme, Position, Token};
use syntect::parsing::{ParseState, ScopeStack, ScopeStackOp, SyntaxDefinition};
use buffer::token::line_iterator::LineIterator;

pub struct TokenIterator<'a> {
    scopes: ScopeStack,
    parser: ParseState,
    lines: LineIterator<'a>,
    current_line: Option<&'a str>,
    current_position: Position,
    line_events: Vec<(usize, ScopeStackOp)>,
}

impl<'a> TokenIterator<'a> {
    pub fn new(data: &'a str, def: &SyntaxDefinition) -> TokenIterator<'a> {
        let mut token_iterator = TokenIterator{
            scopes: ScopeStack::new(),
            parser: ParseState::new(def),
            lines: LineIterator::new(data),
            current_line: None,
            current_position: Position{ line: 0, offset: 0 },
            line_events: Vec::new(),
        };

        // Preload the first line
        token_iterator.parse_next_line();

        token_iterator
    }

    fn next_token(&mut self) -> Option<Token<'a>> {
        // Try to fetch a token from the current line.
        if let Some(token) = self.build_next_token() {
            return Some(token)
        }

        // We're done with this line; on to the next.
        self.parse_next_line();
        if self.current_line.is_some() {
            Some(Token::Newline)
        } else {
            None
        }
    }

    fn build_next_token(&mut self) -> Option<Token<'a>> {
        let mut lexeme = None;

        if let Some(line) = self.current_line {
            while let Some((event_offset, scope_change)) = self.line_events.pop() {
                // We want to capture the full scope for a given token, so we
                // need to make sure we apply all of them and only capture it
                // once we've moved on to another token/offset.
                if event_offset > self.current_position.offset {
                    lexeme = Some(
                        Token::Lexeme(Lexeme{
                            value: &line[self.current_position.offset..event_offset],
                            scope: self.scopes.clone(),
                            position: self.current_position.clone(),
                        })
                    );
                    self.current_position.offset = event_offset;
                }

                // Apply the scope and keep a reference to it, so
                // that we can pair it with a token later on.
                self.scopes.apply(&scope_change);

                if lexeme.is_some() { return lexeme }
            }

            // We already have discrete variant for newlines,
            // so exclude them when considering content length.
            if let Some(end_of_line) = line.len().checked_sub(1) {
                if self.current_position.offset < end_of_line {
                    // The rest of the line hasn't triggered a scope
                    // change; categorize it with the last known scope.
                    lexeme = Some(
                        Token::Lexeme(Lexeme{
                            value: &line[self.current_position.offset..end_of_line],
                            scope: self.scopes.clone(),
                            position: self.current_position.clone(),
                        })
                    );
                }

            };
        }
        self.current_line = None;

        if lexeme.is_some() {
            lexeme
        } else {
            None
        }
    }

    fn parse_next_line(&mut self) {
        if let Some((line_number, line)) = self.lines.next() {
            // We reverse the line elements so that we can pop them off one at a
            // time, handling each event while allowing us to stop at any point.
            let mut line_events = self.parser.parse_line(line);
            line_events.reverse();
            self.line_events = line_events;

            // Keep a reference to the line so that we can create slices of it.
            self.current_line = Some(line);

            // Track our position, which we'll pass to generated tokens.
            self.current_position = Position{ line: line_number, offset: 0 };
        } else {
            self.current_line = None;
        }
    }
}

impl<'a> Iterator for TokenIterator<'a> {
    type Item = Token<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        self.next_token()
    }
}

#[cfg(test)]
mod tests {
    use super::TokenIterator;
    use buffer::{Lexeme, Position, ScopeStack, Token};
    use syntect::parsing::{Scope, SyntaxSet};

    #[test]
    fn token_iterator_returns_correct_tokens() {
        let syntax_set = SyntaxSet::load_defaults_newlines();
        let def = syntax_set.find_syntax_by_extension("rs");
        let iterator = TokenIterator::new("struct Buffer {\n// comment\n  data: String\n}garbage\n\n", def.unwrap());
        let mut scope_stack = ScopeStack::new();
        let mut expected_tokens = Vec::new();
        scope_stack.push(Scope::new("source.rust").unwrap());
        scope_stack.push(Scope::new("meta.struct.rust").unwrap());
        scope_stack.push(Scope::new("storage.type.struct.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "struct",
            scope: scope_stack.clone(),
            position: Position{ line: 0, offset: 0 }
        }));
        scope_stack.pop();
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: " ",
            scope: scope_stack.clone(),
            position: Position{ line: 0, offset: 6 }
        }));
        scope_stack.push(Scope::new("entity.name.struct.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "Buffer",
            scope: scope_stack.clone(),
            position: Position{ line: 0, offset: 7 }
        }));
        scope_stack.pop();
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: " ",
            scope: scope_stack.clone(),
            position: Position{ line: 0, offset: 13 }
        }));
        scope_stack.push(Scope::new("meta.block.rust").unwrap());
        scope_stack.push(Scope::new("punctuation.definition.block.begin.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "{",
            scope: scope_stack.clone(),
            position: Position{ line: 0, offset: 14 }
        }));
        expected_tokens.push(Token::Newline);
        scope_stack.pop();
        scope_stack.push(Scope::new("comment.line.double-slash.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "// comment",
            scope: scope_stack.clone(),
            position: Position{ line: 1, offset: 0 }
        }));
        expected_tokens.push(Token::Newline);
        scope_stack.pop();
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "  ",
            scope: scope_stack.clone(),
            position: Position{ line: 2, offset: 0 }
        }));
        scope_stack.push(Scope::new("variable.other.property.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "data",
            scope: scope_stack.clone(),
            position: Position{ line: 2, offset: 2 }
        }));
        scope_stack.pop();
        scope_stack.push(Scope::new("punctuation.separator.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: ":",
            scope: scope_stack.clone(),
            position: Position{ line: 2, offset: 6 }
        }));
        scope_stack.pop();
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: " String",
            scope: scope_stack.clone(),
            position: Position{ line: 2, offset: 7 }
        }));
        expected_tokens.push(Token::Newline);
        scope_stack.push(Scope::new("punctuation.definition.block.end.rust").unwrap());
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "}",
            scope: scope_stack.clone(),
            position: Position{ line: 3, offset: 0 }
        }));
        scope_stack.pop();
        scope_stack.pop();
        scope_stack.pop();
        expected_tokens.push(Token::Lexeme(Lexeme{
            value: "garbage",
            scope: scope_stack.clone(),
            position: Position{ line: 3, offset: 1 }
        }));
        expected_tokens.push(Token::Newline);
        expected_tokens.push(Token::Newline);
        let actual_tokens: Vec<Token> = iterator.collect();
        for (index, token) in expected_tokens.into_iter().enumerate() {
            assert_eq!(token, actual_tokens[index]);
        }

        //assert_eq!(expected_tokens, actual_tokens);
    }

    #[test]
    fn token_iterator_handles_content_without_trailing_newline() {
        let syntax_set = SyntaxSet::load_defaults_newlines();
        let def = syntax_set.find_syntax_by_extension("rs");
        let iterator = TokenIterator::new("struct", def.unwrap());
        let mut expected_tokens = Vec::new();
        expected_tokens.push(
            Token::Lexeme(Lexeme{
                value: "struct",
                scope: ScopeStack::from_vec(vec![
                    Scope::new("source.rust").unwrap(),
                    Scope::new("storage.type.struct.rust").unwrap()
                ]),
                position: Position{ line: 0, offset: 0 }
            })
        );
        let actual_tokens: Vec<Token> = iterator.collect();
        for (index, token) in expected_tokens.into_iter().enumerate() {
            assert_eq!(token, actual_tokens[index]);
        }

        //assert_eq!(expected_tokens, actual_tokens);
    }
}
<|endoftext|>


<commit_before>//! Log all requests in a format similar to Heroku's router, but with additional
//! information that we care about like User-Agent

use super::prelude::*;
use crate::util::request_header;

use conduit::{header, RequestExt, StatusCode};
use conduit_cookie::RequestSession;

use std::fmt::{self, Display, Formatter};
use std::time::{SystemTime, UNIX_EPOCH};

const SLOW_REQUEST_THRESHOLD_MS: u64 = 1000;

#[derive(Default)]
pub(super) struct LogRequests();

struct OriginalPath(String);

impl Middleware for LogRequests {
    fn before(&self, req: &mut dyn RequestExt) -> BeforeResult {
        let path = OriginalPath(req.path().to_string());
        req.mut_extensions().insert(path);
        Ok(())
    }

    fn after(&self, req: &mut dyn RequestExt, res: AfterResult) -> AfterResult {
        let response_time =
            if let Ok(start_ms) = request_header(req, "x-request-start").parse::<u128>() {
                let current_ms = SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .expect("Time went way backwards")
                    .as_millis();

                if current_ms > start_ms {
                    // The result cannot be negative
                    current_ms - start_ms
                } else {
                    // Because our nginx proxy and app run on the same dyno in production, we
                    // shouldn't have to worry about clock drift. But if something goes wrong,
                    // calculate the response time based on when the request reached this app.
                    fallback_response_time(req)
                }
            } else {
                // X-Request-Start header couldn't be parsed.
                // We are probably running locally and not behind nginx.
                fallback_response_time(req)
            };

        // This will only trucate for requests lasting > 500 million years
        let response_time = response_time as u64;

        println!(
            "{}",
            RequestLine {
                req,
                res: &res,
                response_time,
            }
        );

        report_to_sentry(req, &res, response_time);

        res
    }
}

/// Calculate the response time based on when the request reached the in-app web server.
///
/// This serves as a fallback in case the `X-Request-Start` header is missing or invalid.
fn fallback_response_time(req: &mut dyn RequestExt) -> u128 {
    req.elapsed().as_millis()
}

struct CustomMetadata {
    entries: Vec<(&'static str, String)>,
}

pub fn add_custom_metadata<V: Display>(req: &mut dyn RequestExt, key: &'static str, value: V) {
    if let Some(metadata) = req.mut_extensions().find_mut::<CustomMetadata>() {
        metadata.entries.push((key, value.to_string()));
    } else {
        let mut metadata = CustomMetadata {
            entries: Vec::new(),
        };
        metadata.entries.push((key, value.to_string()));
        req.mut_extensions().insert(metadata);
    }

    sentry::configure_scope(|scope| scope.set_extra(key, value.to_string().into()));
}

fn report_to_sentry(req: &dyn RequestExt, res: &AfterResult, response_time: u64) {
    sentry::configure_scope(|scope| {
        {
            let id = req.session().get("user_id").map(|str| str.to_string());

            let user = sentry::User {
                id,
                ..Default::default()
            };

            scope.set_user(Some(user));
        }

        if let Some(request_id) = req
            .headers()
            .get("x-request-id")
            .and_then(|value| value.to_str().ok())
        {
            scope.set_tag("request.id", request_id);
        }

        {
            let status = res
                .as_ref()
                .map(|resp| resp.status())
                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);

            scope.set_tag("response.status", status.as_str());
        }

        scope.set_extra("Response time [ms]", response_time.into());
    });
}

#[cfg(test)]
pub(crate) fn get_log_message(req: &dyn RequestExt, key: &'static str) -> String {
    // Unwrap shouldn't panic as no other code has access to the private struct to remove it
    for (k, v) in &req.extensions().find::<CustomMetadata>().unwrap().entries {
        if key == *k {
            return v.clone();
        }
    }
    panic!("expected log message for {} not found", key);
}

struct RequestLine<'r> {
    req: &'r dyn RequestExt,
    res: &'r AfterResult,
    response_time: u64,
}

impl Display for RequestLine<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let mut line = LogLine::new(f);

        let status = self.res.as_ref().map(|res| res.status());
        let status = status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);

        let at = if status.is_server_error() {
            "error"
        } else {
            "info"
        };

        line.add_field("at", at)?;
        line.add_field("method", self.req.method())?;
        line.add_quoted_field("path", FullPath(self.req))?;

        // The request_id is not logged for successful download requests
        if !(self.req.path().ends_with("/download")
            && self
                .res
                .as_ref()
                .ok()
                .map(|ok| ok.status().is_redirection())
                == Some(true))
        {
            line.add_field("request_id", request_header(self.req, "x-request-id"))?;
        }

        line.add_quoted_field("fwd", request_header(self.req, "x-real-ip"))?;
        line.add_field("service", TimeMs(self.response_time))?;
        line.add_field("status", status.as_str())?;
        line.add_quoted_field("user_agent", request_header(self.req, header::USER_AGENT))?;

        if let Some(metadata) = self.req.extensions().find::<CustomMetadata>() {
            for (key, value) in &metadata.entries {
                line.add_quoted_field(key, value)?;
            }
        }

        if let Err(err) = self.res {
            line.add_quoted_field("error", err)?;
        }

        if self.response_time > SLOW_REQUEST_THRESHOLD_MS {
            line.add_marker("SLOW REQUEST")?;
        }

        Ok(())
    }
}

struct FullPath<'a>(&'a dyn RequestExt);

impl<'a> Display for FullPath<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        // Unwrap shouldn't panic as no other code has access to the private struct to remove it
        write!(
            f,
            "{}",
            self.0.extensions().find::<OriginalPath>().unwrap().0
        )?;
        if let Some(q_string) = self.0.query_string() {
            write!(f, "?{}", q_string)?;
        }
        Ok(())
    }
}

struct TimeMs(u64);

impl Display for TimeMs {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)?;
        f.write_str("ms")?;
        Ok(())
    }
}

struct LogLine<'f, 'g> {
    f: &'f mut Formatter<'g>,
    first: bool,
}

impl<'f, 'g> LogLine<'f, 'g> {
    fn new(f: &'f mut Formatter<'g>) -> Self {
        Self { f, first: true }
    }

    fn add_field<K: Display, V: Display>(&mut self, key: K, value: V) -> fmt::Result {
        self.start_item()?;

        key.fmt(self.f)?;
        self.f.write_str("=")?;
        value.fmt(self.f)?;

        Ok(())
    }

    fn add_quoted_field<K: Display, V: Display>(&mut self, key: K, value: V) -> fmt::Result {
        self.start_item()?;

        key.fmt(self.f)?;
        self.f.write_str("=\"")?;
        value.fmt(self.f)?;
        self.f.write_str("\"")?;

        Ok(())
    }

    fn add_marker<M: Display>(&mut self, marker: M) -> fmt::Result {
        self.start_item()?;

        marker.fmt(self.f)?;

        Ok(())
    }

    fn start_item(&mut self) -> fmt::Result {
        if !self.first {
            self.f.write_str(" ")?;
        }
        self.first = false;
        Ok(())
    }
}
<commit_msg>middleware::log_request: Move `request.id` handling for Sentry into `before()` method<commit_after>//! Log all requests in a format similar to Heroku's router, but with additional
//! information that we care about like User-Agent

use super::prelude::*;
use crate::util::request_header;

use conduit::{header, RequestExt, StatusCode};
use conduit_cookie::RequestSession;

use std::fmt::{self, Display, Formatter};
use std::time::{SystemTime, UNIX_EPOCH};

const SLOW_REQUEST_THRESHOLD_MS: u64 = 1000;

#[derive(Default)]
pub(super) struct LogRequests();

struct OriginalPath(String);

impl Middleware for LogRequests {
    fn before(&self, req: &mut dyn RequestExt) -> BeforeResult {
        let path = OriginalPath(req.path().to_string());
        req.mut_extensions().insert(path);

        if let Some(request_id) = req
            .headers()
            .get("x-request-id")
            .and_then(|value| value.to_str().ok())
        {
            sentry::configure_scope(|scope| scope.set_tag("request.id", request_id));
        }

        Ok(())
    }

    fn after(&self, req: &mut dyn RequestExt, res: AfterResult) -> AfterResult {
        let response_time =
            if let Ok(start_ms) = request_header(req, "x-request-start").parse::<u128>() {
                let current_ms = SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .expect("Time went way backwards")
                    .as_millis();

                if current_ms > start_ms {
                    // The result cannot be negative
                    current_ms - start_ms
                } else {
                    // Because our nginx proxy and app run on the same dyno in production, we
                    // shouldn't have to worry about clock drift. But if something goes wrong,
                    // calculate the response time based on when the request reached this app.
                    fallback_response_time(req)
                }
            } else {
                // X-Request-Start header couldn't be parsed.
                // We are probably running locally and not behind nginx.
                fallback_response_time(req)
            };

        // This will only trucate for requests lasting > 500 million years
        let response_time = response_time as u64;

        println!(
            "{}",
            RequestLine {
                req,
                res: &res,
                response_time,
            }
        );

        report_to_sentry(req, &res, response_time);

        res
    }
}

/// Calculate the response time based on when the request reached the in-app web server.
///
/// This serves as a fallback in case the `X-Request-Start` header is missing or invalid.
fn fallback_response_time(req: &mut dyn RequestExt) -> u128 {
    req.elapsed().as_millis()
}

struct CustomMetadata {
    entries: Vec<(&'static str, String)>,
}

pub fn add_custom_metadata<V: Display>(req: &mut dyn RequestExt, key: &'static str, value: V) {
    if let Some(metadata) = req.mut_extensions().find_mut::<CustomMetadata>() {
        metadata.entries.push((key, value.to_string()));
    } else {
        let mut metadata = CustomMetadata {
            entries: Vec::new(),
        };
        metadata.entries.push((key, value.to_string()));
        req.mut_extensions().insert(metadata);
    }

    sentry::configure_scope(|scope| scope.set_extra(key, value.to_string().into()));
}

fn report_to_sentry(req: &dyn RequestExt, res: &AfterResult, response_time: u64) {
    sentry::configure_scope(|scope| {
        {
            let id = req.session().get("user_id").map(|str| str.to_string());

            let user = sentry::User {
                id,
                ..Default::default()
            };

            scope.set_user(Some(user));
        }

        {
            let status = res
                .as_ref()
                .map(|resp| resp.status())
                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);

            scope.set_tag("response.status", status.as_str());
        }

        scope.set_extra("Response time [ms]", response_time.into());
    });
}

#[cfg(test)]
pub(crate) fn get_log_message(req: &dyn RequestExt, key: &'static str) -> String {
    // Unwrap shouldn't panic as no other code has access to the private struct to remove it
    for (k, v) in &req.extensions().find::<CustomMetadata>().unwrap().entries {
        if key == *k {
            return v.clone();
        }
    }
    panic!("expected log message for {} not found", key);
}

struct RequestLine<'r> {
    req: &'r dyn RequestExt,
    res: &'r AfterResult,
    response_time: u64,
}

impl Display for RequestLine<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let mut line = LogLine::new(f);

        let status = self.res.as_ref().map(|res| res.status());
        let status = status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);

        let at = if status.is_server_error() {
            "error"
        } else {
            "info"
        };

        line.add_field("at", at)?;
        line.add_field("method", self.req.method())?;
        line.add_quoted_field("path", FullPath(self.req))?;

        // The request_id is not logged for successful download requests
        if !(self.req.path().ends_with("/download")
            && self
                .res
                .as_ref()
                .ok()
                .map(|ok| ok.status().is_redirection())
                == Some(true))
        {
            line.add_field("request_id", request_header(self.req, "x-request-id"))?;
        }

        line.add_quoted_field("fwd", request_header(self.req, "x-real-ip"))?;
        line.add_field("service", TimeMs(self.response_time))?;
        line.add_field("status", status.as_str())?;
        line.add_quoted_field("user_agent", request_header(self.req, header::USER_AGENT))?;

        if let Some(metadata) = self.req.extensions().find::<CustomMetadata>() {
            for (key, value) in &metadata.entries {
                line.add_quoted_field(key, value)?;
            }
        }

        if let Err(err) = self.res {
            line.add_quoted_field("error", err)?;
        }

        if self.response_time > SLOW_REQUEST_THRESHOLD_MS {
            line.add_marker("SLOW REQUEST")?;
        }

        Ok(())
    }
}

struct FullPath<'a>(&'a dyn RequestExt);

impl<'a> Display for FullPath<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        // Unwrap shouldn't panic as no other code has access to the private struct to remove it
        write!(
            f,
            "{}",
            self.0.extensions().find::<OriginalPath>().unwrap().0
        )?;
        if let Some(q_string) = self.0.query_string() {
            write!(f, "?{}", q_string)?;
        }
        Ok(())
    }
}

struct TimeMs(u64);

impl Display for TimeMs {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)?;
        f.write_str("ms")?;
        Ok(())
    }
}

struct LogLine<'f, 'g> {
    f: &'f mut Formatter<'g>,
    first: bool,
}

impl<'f, 'g> LogLine<'f, 'g> {
    fn new(f: &'f mut Formatter<'g>) -> Self {
        Self { f, first: true }
    }

    fn add_field<K: Display, V: Display>(&mut self, key: K, value: V) -> fmt::Result {
        self.start_item()?;

        key.fmt(self.f)?;
        self.f.write_str("=")?;
        value.fmt(self.f)?;

        Ok(())
    }

    fn add_quoted_field<K: Display, V: Display>(&mut self, key: K, value: V) -> fmt::Result {
        self.start_item()?;

        key.fmt(self.f)?;
        self.f.write_str("=\"")?;
        value.fmt(self.f)?;
        self.f.write_str("\"")?;

        Ok(())
    }

    fn add_marker<M: Display>(&mut self, marker: M) -> fmt::Result {
        self.start_item()?;

        marker.fmt(self.f)?;

        Ok(())
    }

    fn start_item(&mut self) -> fmt::Result {
        if !self.first {
            self.f.write_str(" ")?;
        }
        self.first = false;
        Ok(())
    }
}
<|endoftext|>