~nicohman/ravenlib

3ee2e138f54cb4f23c7a58a66cdb6087836fa4e8 — Nico Hickman 1 year, 9 months ago 7281045
Add log crate logging
4 files changed, 185 insertions(+), 132 deletions(-)

M Cargo.toml
M src/lib.rs
M src/ravenserver.rs
M src/themes.rs
M Cargo.toml => Cargo.toml +2 -4
@@ 19,11 19,9 @@ tar = "0.4"
reqwest = "0.9.5"
multipart = "0.15.2"
dirs = "1.0"
log = "0.4"
error-chain = "0.12.0"
[dev-dependencies]
time = "0.1"
human-panic = "1.0.1"
structopt = "0.2.10"
[features]
default = ["logging"]
logging = []
structopt = "0.2.10"
\ No newline at end of file

M src/lib.rs => src/lib.rs +29 -21
@@ 10,9 10,11 @@ extern crate reqwest;
extern crate tar;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate log;
pub mod error;
/// Interactions with online instances of ThemeHub
pub mod ravenserver;
pub mod error;
use std::fs::DirEntry;
/// Module for theme manipulation
pub mod themes;


@@ 20,9 22,9 @@ pub mod themes;
pub mod config {
    use crate::themes::*;
    use dirs::home_dir;
    use error::*;
    use serde_json::value::Map;
    use std::{fs, fs::OpenOptions, io::Read, io::Write};
    use error::*;
    /// Returns home directory as string
    pub fn get_home() -> String {
        return String::from(home_dir().unwrap().to_str().unwrap());


@@ 67,6 69,7 @@ pub mod config {
    pub fn check_themes() -> Result<()> {
        let entries = get_themes()?;
        for entry in entries {
            info!("Checking theme {}", entry);
            if fs::metadata(get_home() + "/.config/raven/themes/" + &entry + "/theme").is_ok() {
                convert_theme(entry)?;
            }


@@ 79,19 82,19 @@ pub mod config {
            fs::create_dir(get_home() + "/.config/raven")?;
            fs::create_dir(get_home() + "/.config/raven/themes")?;
        } else {
            println!(
            error!(
                    "The config file format has changed. Please check ~/.config/raven/config.json to reconfigure raven."
                );

        }
        info!("Creating config file");
        let mut file = OpenOptions::new()
            .create(true)
            .write(true)
            .open(get_home() + "/.config/raven/config.json")?;
        let default = serde_json::to_string(&Config::default())?;
        info!("Writing default config to file");
        file.write_all(default.as_bytes())?;
        #[cfg(feature = "logging")]
        println!("Correctly initialized base config and directory structure.");
        info!("Correctly initialized base config and directory structure.");
        Ok(())
    }
    /// Checks to see if base config/directories need to be initialized


@@ 101,16 104,19 @@ pub mod config {
            || fs::metadata(get_home() + "/.config/raven/themes").is_err()
    }
    /// Updates and replaces the stored config with a new config
    pub fn up_config(conf: Config) -> Result<Config>{
    pub fn up_config(conf: Config) -> Result<Config> {
        info!("Opening and writing to temp config file");
        OpenOptions::new()
            .create(true)
            .write(true)
            .open(get_home() + "/.config/raven/~config.json")?
            .write_all(serde_json::to_string(&conf)?.as_bytes())?;
        info!("Copying temp file to config file");
        fs::copy(
            get_home() + "/.config/raven/~config.json",
            get_home() + "/.config/raven/config.json",
        )?;
        info!("Removing temp file");
        fs::remove_file(get_home() + "/.config/raven/~config.json")?;
        Ok(conf)
    }


@@ 118,12 124,15 @@ pub mod config {
    pub fn up_theme(theme: ThemeStore) -> Result<ThemeStore> {
        let wthemepath = get_home() + "/.config/raven/themes/" + &theme.name + "/~theme.json";
        let themepath = get_home() + "/.config/raven/themes/" + &theme.name + "/theme.json";
        info!("Opening and writing to temp theme store");
        OpenOptions::new()
            .create(true)
            .write(true)
            .open(&wthemepath)?
            .write_all(serde_json::to_string(&theme)?.as_bytes())?;
        info!("Copying temp theme store to final");
        fs::copy(&wthemepath, &themepath)?;
        info!("Removing temp file");
        fs::remove_file(&wthemepath)?;
        Ok(theme)
    }


@@ 166,49 175,48 @@ pub mod config {
    {
        let theme = theme.into();
        let mut st = String::new();
        info!("Opening and reading theme store {}", theme);
        fs::File::open(get_home() + "/.config/raven/themes/" + &theme + "/theme.json")?
            .read_to_string(&mut st)?;
        info!("Parsing theme store");
        let result = serde_json::from_str(&st)?;
        Ok(result)
    }
    /// Retrieve config settings from file
    pub fn get_config() -> Result<Config> {
        let mut conf = String::new();
        fs::File::open(get_home() + "/.config/raven/config.json")?
            .read_to_string(&mut conf)?;
        info!("Opening and reading config file");
        fs::File::open(get_home() + "/.config/raven/config.json")?.read_to_string(&mut conf)?;
        info!("Parsing config file");
        Ok(serde_json::from_str(&conf)?)
    }
}
/// Ravend control
pub mod daemon {
    use error::*;
    use std::process::Command;
    use std::process::Child;
    use std::process::Command;
    /// Starts ravend
    pub fn start_daemon() -> Result<Child> {
        let child = Command::new("sh")
            .arg("-c")
            .arg("ravend")
            .spawn()?;
        #[cfg(feature = "logging")]
        println!("Started cycle daemon.");
        info!("Starting ravend");
        let child = Command::new("sh").arg("-c").arg("ravend").spawn()?;
        info!("Started cycle daemon.");
        Ok(child)
    }
    /// Stops ravend
    pub fn stop_daemon() -> Result<()> {
        info!("Starting pkill command");
        Command::new("pkill")
            .arg("-SIGKILL")
            .arg("ravend")
            .output()?;
        #[cfg(feature = "logging")]
        println!("Stopped cycle daemon.");
        info!("Stopped cycle daemon.");
        Ok(())
    }
    /// Checks if the ravend daemon is running
    pub fn check_daemon() -> Result<bool> {
        let out = Command::new("ps")
            .arg("aux")
            .output()?;
        info!("Starting ps command");
        let out = Command::new("ps").arg("aux").output()?;
        let form_out = String::from_utf8_lossy(&out.stdout);
        let line_num = form_out.lines().filter(|x| x.contains("ravend")).count();
        Ok(line_num > 0)

M src/ravenserver.rs => src/ravenserver.rs +72 -77
@@ 32,9 32,11 @@ pub struct MetaRes {
pub fn load_info() -> Result<UserInfo> {
    if fs::metadata(get_home() + "/.config/raven/ravenserver.json").is_ok() {
        let mut info = String::new();
        info!("Opening and reading user info file");
        File::open(get_home() + "/.config/raven/ravenserver.json")?.read_to_string(&mut info)?;
        return Ok(serde_json::from_str(&info)?);
    } else {
        error!("Could not load user info");
        Err(ErrorKind::Server(RavenServerErrorKind::NotLoggedIn.into()).into())
    }
}


@@ 43,26 45,28 @@ pub fn export<N>(theme_name: N, tmp: bool) -> Result<String>
where
    N: Into<String>,
{
    info!("Exporting theme");
    let theme_name = theme_name.into();
    if fs::metadata(get_home() + "/.config/raven/themes/" + &theme_name).is_ok() {
        let mut tname = String::new();
        if tmp {
            info!("Using temp directory /tmp");
            tname = tname + "/tmp/";
        }
        tname = tname + &theme_name.to_string() + ".tar";
        info!("Creating output file");
        let tb = File::create(&tname)?;
        let mut b = Builder::new(tb);
        info!("Importing theme into tar builder");
        b.append_dir_all(
            theme_name.to_string(),
            get_home() + "/.config/raven/themes/" + &theme_name,
        )?;
        b.into_inner()?;
        #[cfg(feature = "logging")]
        println!("Wrote theme to {}", tname);
        info!("Wrote theme to {}", tname);
        Ok(tname)
    } else {
        #[cfg(feature = "logging")]
        println!("Theme does not exist");
        error!("Theme does not exist");
        Err(ErrorKind::InvalidThemeName(theme_name).into())
    }
}


@@ 72,31 76,37 @@ where
    N: Into<String>,
{
    let fname: String = file_name.into();
    info!("Opening theme file");
    let fd = File::open(fname)?;
    info!("Converting opened file to archive reader");
    let mut arch = Archive::new(fd);
    info!("Unpacking archive");
    arch.unpack(get_home() + "/.config/raven/themes/")?;
    #[cfg(feature = "logging")]
    println!("Imported theme.");
    info!("Imported theme.");
    Ok(())
}
/// Replaces and updates a stored userinfo file
fn up_info(inf: UserInfo) -> Result<()> {
    info!("Updating stored userinfo");
    let winfpath = get_home() + "/.config/raven/~ravenserver.json";
    let infpath = get_home() + "/.config/raven/ravenserver.json";
    info!("Opening temp file and writing to it");
    OpenOptions::new()
        .create(true)
        .write(true)
        .open(&winfpath)?
        .write_all(serde_json::to_string(&inf)?.as_bytes())
        .expect("Couldn't write to user info file");
    info!("Copying temp file to final file");
    fs::copy(&winfpath, &infpath)?;
    info!("Removing temp file");
    fs::remove_file(&winfpath)?;
    Ok(())
}
/// Logs a user out by deleting the userinfo file
pub fn logout() -> Result<()> {
    info!("Removing ravenserver config file");
    fs::remove_file(get_home() + "/.config/raven/ravenserver.json")?;
    #[cfg(feature = "logging")]
    println!("Successfully logged you out");
    Ok(())
}


@@ 112,6 122,7 @@ where
{
    let info = load_info()?;
    let client = reqwest::Client::new();
    info!("Making delete post request");
    let res = client
        .post(
            &(get_host()?


@@ 125,26 136,21 @@ where
        .send()?;

    if res.status().is_success() {
        #[cfg(feature = "logging")]
        println!("Successfully deleted user and all owned themes. Logging out");
        logout()?;
        Ok(())
    } else {
        if res.status() == reqwest::StatusCode::FORBIDDEN {
            #[cfg(feature = "logging")]
            println!("You are trying to delete a user you are not. Bad!");
            error!("You are trying to delete a user you are not. Bad!");
            Err(ErrorKind::Server(RavenServerErrorKind::PermissionDenied.into()).into())
        } else if res.status() == reqwest::StatusCode::UNAUTHORIZED {
            #[cfg(feature = "logging")]
            println!("You're trying to delete a user w/o providing authentication credentials");
            error!("You're trying to delete a user w/o providing authentication credentials");
            Err(ErrorKind::Server(RavenServerErrorKind::NotLoggedIn.into()).into())
        } else if res.status() == reqwest::StatusCode::NOT_FOUND {
            #[cfg(feature = "logging")]
            println!("You're trying to delete a user that doesn't exist");
            error!("You're trying to delete a user that doesn't exist");
            Err(ErrorKind::Server(RavenServerErrorKind::DoesNotExist(info.name).into()).into())
        } else {
            #[cfg(feature = "logging")]
            println!("Server error. Code {:?}", res.status());
            error!("Server error. Code {:?}", res.status());
            Err(ErrorKind::Server(RavenServerErrorKind::ServerError(res.status()).into()).into())
        }
    }


@@ 158,27 164,24 @@ pub fn create_user(
    let (name, pass, pass2): (String, String, String) = (name.into(), pass.into(), pass2.into());
    if pass == pass2 {
        let client = reqwest::Client::new();
        info!("Making create post request");
        let res = client
            .post(&(get_host()? + "/themes/user/create?name=" + &name + "&pass=" + &pass))
            .send()?;
        if res.status().is_success() {
            #[cfg(feature = "logging")]
            println!("Successfully created user. Sign in with `raven login [name] [password]`");
            Ok(true)
        } else {
            if res.status() == reqwest::StatusCode::FORBIDDEN {
                #[cfg(feature = "logging")]
                println!("User already created. Pick a different name!");
                error!("User already created. Pick a different name!");
                Err(ErrorKind::Server(RavenServerErrorKind::PermissionDenied.into()).into())
            } else if res.status() == reqwest::StatusCode::PAYLOAD_TOO_LARGE {
                #[cfg(feature = "logging")]
                println!(
                error!(
                            "Either your username or password was too long. The limit is 20 characters for username, and 100 for password."
                        );
                Err(ErrorKind::Server(RavenServerErrorKind::TooLarge.into()).into())
            } else {
                #[cfg(feature = "logging")]
                println!("Server error. Code {:?}", res.status());
                error!("Server error. Code {:?}", res.status());
                Err(
                    ErrorKind::Server(RavenServerErrorKind::ServerError(res.status()).into())
                        .into(),


@@ 186,8 189,7 @@ pub fn create_user(
            }
        }
    } else {
        #[cfg(feature = "logging")]
        println!("Passwords need to match");
        error!("Passwords need to match");
        return Ok(false);
    }
}


@@ 200,7 202,9 @@ where
    let info = load_info()?;
    if fs::metadata(get_home() + "/.config/raven/themes/" + &name).is_ok() {
        let tname = export(name.as_str(), true)?;
        info!("Creating multipart upload form");
        let form = reqwest::multipart::Form::new().file("fileupload", &tname)?;
        info!("Making upload post request");
        let res = reqwest::Client::new()
            .post(&(get_host()? + "/themes/upload?name=" + &name + "&token=" + &info.token))
            .multipart(form)


@@ 208,25 212,24 @@ where
        if res.status().is_success() {
            let mut up = false;
            if res.status() == reqwest::StatusCode::CREATED {
                #[cfg(feature = "logging")]
                println!("Theme successfully uploaded.");
                info!("Theme successfully uploaded.");
                up = true;
            }
            let theme_st = load_store(name.as_str())?;
            if theme_st.screenshot != default_screen() {
                info!("Publishing screenshot metadata");
                pub_metadata(name.as_str(), "screen".into(), &theme_st.screenshot)?;
            }
            info!("Publishing description metadata");
            pub_metadata(name, "description".into(), theme_st.description)?;
            fs::remove_file(tname)?;
            Ok(up)
        } else {
            if res.status() == reqwest::StatusCode::FORBIDDEN {
                #[cfg(feature = "logging")]
                println!("That theme already exists, and you are not its owner.");
                error!("That theme already exists, and you are not its owner.");
                Err(ErrorKind::Server(RavenServerErrorKind::PermissionDenied.into()).into())
            } else {
                #[cfg(feature = "logging")]
                println!("Server error. Code {:?}", res.status());
                error!("Server error. Code {:?}", res.status());
                Err(
                    ErrorKind::Server(RavenServerErrorKind::ServerError(res.status()).into())
                        .into(),


@@ 234,8 237,7 @@ where
            }
        }
    } else {
        #[cfg(feature = "logging")]
        println!("That theme does not exist");
        error!("That theme does not exist");
        Err(ErrorKind::InvalidThemeName(name).into())
    }
}


@@ 246,6 248,7 @@ where
{
    let name = name.into();
    let client = reqwest::Client::new();
    info!("Making metadata get request");
    let mut res = client
        .get(&(get_host()? + "/themes/meta/" + &name))
        .send()?;


@@ 254,8 257,10 @@ where
        Ok(meta)
    } else {
        if res.status() == reqwest::StatusCode::NOT_FOUND {
            error!("Metadata does not exist on server");
            Err(ErrorKind::Server(RavenServerErrorKind::DoesNotExist(name).into()).into())
        } else {
            error!("Server error: {:?}", res.status());
            Err(ErrorKind::Server(RavenServerErrorKind::ServerError(res.status()).into()).into())
        }
    }


@@ 268,6 273,7 @@ where
    let info = load_info()?;
    let name = name.into();
    let client = reqwest::Client::new();
    info!("Making metadata publish request");
    let res = client
        .post(
            &(get_host()?


@@ 282,34 288,28 @@ where
        )
        .send()?;
    if res.status().is_success() {
        #[cfg(feature = "logging")]
        println!("Successfully updated theme metadata");
        info!("Successfully updated theme metadata");
        Ok(())
    } else {
        if res.status() == reqwest::StatusCode::NOT_FOUND {
            #[cfg(feature = "logging")]
            println!("That theme hasn't been published");
            error!("That theme hasn't been published");
            Err(ErrorKind::Server(RavenServerErrorKind::DoesNotExist(name).into()).into())
        } else if res.status() == reqwest::StatusCode::FORBIDDEN {
            #[cfg(feature = "logging")]
            println!("Can't edit the metadata of a theme that isn't yours");
            error!("Can't edit the metadata of a theme that isn't yours");
            Err(ErrorKind::Server(RavenServerErrorKind::PermissionDenied.into()).into())
        } else if res.status() == reqwest::StatusCode::PRECONDITION_FAILED {
            #[cfg(feature = "logging")]
            println!("That isn't a valid metadata type");
            error!("That isn't a valid metadata type");
            Err(ErrorKind::Server(
                RavenServerErrorKind::PreConditionFailed("metadata type".to_string()).into(),
            )
            .into())
        } else if res.status() == reqwest::StatusCode::PAYLOAD_TOO_LARGE {
            #[cfg(feature = "logging")]
            println!(
            error!(
                        "Your description or screenshot url was more than 200 characters long. Please shorten it."
                    );
            Err(ErrorKind::Server(RavenServerErrorKind::TooLarge.into()).into())
        } else {
            #[cfg(feature = "logging")]
            println!("Server error. Code {:?}", res.status());
            error!("Server error. Code {:?}", res.status());
            Err(ErrorKind::Server(RavenServerErrorKind::ServerError(res.status()).into()).into())
        }
    }


@@ 326,25 326,20 @@ where
        .post(&(get_host()? + "/themes/delete/" + &name + "?token=" + &info.token))
        .send()?;
    if res.status().is_success() {
        #[cfg(feature = "logging")]
        println!("Successfully unpublished theme");
        info!("Successfully unpublished theme");
        Ok(())
    } else {
        if res.status() == reqwest::StatusCode::NOT_FOUND {
            #[cfg(feature = "logging")]
            println!("Can't unpublish a nonexistent theme");
            error!("Can't unpublish a nonexistent theme");
            Err(ErrorKind::Server(RavenServerErrorKind::DoesNotExist(name).into()).into())
        } else if res.status() == reqwest::StatusCode::FORBIDDEN {
            #[cfg(feature = "logging")]
            println!("Can't unpublish a theme that isn't yours");
            error!("Can't unpublish a theme that isn't yours");
            Err(ErrorKind::Server(RavenServerErrorKind::PermissionDenied.into()).into())
        } else if res.status() == reqwest::StatusCode::UNAUTHORIZED {
            #[cfg(feature = "logging")]
            println!("Did not provide a valid login token");
            error!("Did not provide a valid login token");
            Err(ErrorKind::Server(RavenServerErrorKind::PermissionDenied.into()).into())
        } else {
            #[cfg(feature = "logging")]
            println!("Server error. Code {:?}", res.status());
            error!("Server error. Code {:?}", res.status());
            Err(ErrorKind::Server(RavenServerErrorKind::ServerError(res.status()).into()).into())
        }
    }


@@ 372,18 367,21 @@ where
    N: Into<String>,
{
    let name = name.into();
    info!("Downloading theme {}", name);
    let mut tname = String::new();
    if check_tmp() {
        tname = tname + "/tmp/";
    }
    tname = tname + &name + ".tar";
    println!("{}", tname);
    let client = reqwest::Client::new();
    info!("Requesting theme from server");
    let mut res = client
        .get(&(get_host()? + "/themes/repo/" + &name))
        .send()?;
    if res.status().is_success() {
        info!("Opening file {}", tname);
        let mut file = OpenOptions::new().create(true).write(true).open(&tname)?;
        info!("Copying response to file");
        res.copy_to(&mut file)?;
        println!("Downloaded theme.");
        if res.status() == reqwest::StatusCode::ALREADY_REPORTED && !force {


@@ 398,21 396,22 @@ where
                            "Continuing. Please look carefully at the theme files in ~/.config/raven/themes/{} before loading this theme.",
                            name
                        );
                info!("Importing downloaded theme");
                import(tname.as_str())?;
                #[cfg(feature = "logging")]
                println!("Imported theme. Removing archive.");
                info!("Imported theme. Removing archive.");
                fs::remove_file(&tname)?;
                #[cfg(feature = "logging")]
                println!("Downloading metadata.");
                info!("Downloading metadata.");
                let meta = get_metadata(name.as_str())?;
                let mut st = load_store(name.as_str())?;
                st.screenshot = meta.screen;
                st.description = meta.description;
                info!("Updating local theme store");
                up_theme(st)?;
                if fs::metadata(get_home() + "/.config/raven/themes/" + &name + "/script").is_ok()
                    || fs::metadata(get_home() + "/.config/raven/themes/" + &name + "/lemonbar")
                        .is_ok()
                {
                    info!("Theme has script or lemonbar. Printing higher warning");
                    if !force {
                        install_warning(true);
                    }


@@ 423,32 422,32 @@ where
                }
                Ok(true)
            } else {
                #[cfg(feature = "logging")]
                println!("Removing downloaded archive.");
                info!("Removing downloaded archive.");
                fs::remove_file(&tname)?;
                Ok(false)
            }
        } else {
            if res.status() == reqwest::StatusCode::ALREADY_REPORTED {
                #[cfg(feature = "logging")]
                print!(
                            "This theme has recently been reported, and has not been approved by an admin. It is not advisable to install this theme. Continuing because of --force."
                        );
            }
            info!("Importing theme");
            import(tname.as_str())?;
            #[cfg(feature = "logging")]
            println!("Imported theme. Removing archive.");
            info!("Imported theme. Removing archive.");
            fs::remove_file(tname)?;
            #[cfg(feature = "logging")]
            println!("Downloading metadata.");
            info!("Downloading metadata.");
            let meta = get_metadata(name.as_str())?;
            let mut st = load_store(name.as_str())?;
            st.screenshot = meta.screen;
            st.description = meta.description;
            info!("Updating local theme store");
            up_theme(st)?;
            if fs::metadata(get_home() + "/.config/raven/themes/" + &name + "/script").is_ok()
                || fs::metadata(get_home() + "/.config/raven/themes/" + &name + "/lemonbar").is_ok()
            {
                info!("Theme has script or lemonbar. Printing higher warning");

                if !force {
                    install_warning(true);
                }


@@ 461,12 460,10 @@ where
        }
    } else {
        if res.status() == reqwest::StatusCode::NOT_FOUND {
            #[cfg(feature = "logging")]
            println!("Theme has not been uploaded");
            error!("Theme has not been uploaded");
            Err(ErrorKind::Server(RavenServerErrorKind::DoesNotExist(name).into()).into())
        } else {
            #[cfg(feature = "logging")]
            println!("Server error. Code {:?}", res.status());
            error!("Server error. Code {:?}", res.status());
            Err(ErrorKind::Server(RavenServerErrorKind::ServerError(res.status()).into()).into())
        }
    }


@@ 474,23 471,21 @@ where
/// Logs a user in and writes userinfo file to disk
pub fn login_user(name: impl Into<String>, pass: impl Into<String>) -> Result<()> {
    let client = reqwest::Client::new();
    info!("Making login request");
    let mut res = client
        .get(&(get_host()? + "/themes/user/login?name=" + &name.into() + "&pass=" + &pass.into()))
        .send()?;
    if res.status().is_success() {
        #[cfg(feature = "logging")]
        println!("Successfully signed in. Writing login info to disk.");
        info!("Successfully signed in. Writing login info to disk.");
        let info = res.json()?;
        up_info(info)?;
        Ok(())
    } else {
        if res.status() == reqwest::StatusCode::FORBIDDEN {
            #[cfg(feature = "logging")]
            println!("Wrong login info. Try again!");
            error!("Wrong login info. Try again!");
            Err(ErrorKind::Server(RavenServerErrorKind::PermissionDenied.into()).into())
        } else {
            #[cfg(feature = "logging")]
            println!("Server error. Code {:?}", res.status());
            error!("Server error. Code {:?}", res.status());
            Err(ErrorKind::Server(RavenServerErrorKind::ServerError(res.status()).into()).into())
        }
    }

M src/themes.rs => src/themes.rs +82 -30
@@ 70,6 70,7 @@ impl ROption {
impl Theme {
    /// Loads options held within theme.json key-value storage
    pub fn load_kv(&self) {
        info!("Loading all key-value options");
        for (k, v) in &self.kv {
            self.load_k(k.as_str(), v.as_str().unwrap()).unwrap();
        }


@@ 77,6 78,8 @@ impl Theme {
    /// Loads a single key option
    pub fn load_k(&self, k: impl Into<String>, v: impl Into<String>) -> Result<bool> {
        let (k, v) = (k.into(), v.into());
        info!("Loading key {} with value {}", k, v);

        let mut ok = true;
        match k.as_str() {
            "st_tmtheme" => self.load_sublt("st_tmtheme", v.as_str())?,


@@ 84,14 87,12 @@ impl Theme {
            "st_subltheme" => self.load_sublt("st_subltheme", v.as_str())?,
            "vscode" => self.load_vscode(v.as_str())?,
            _ => {
                #[cfg(feature = "logging")]
                println!("Unrecognized key {}", k);
                warn!("Unrecognized key {}", k);
                ok = false;
                false
            }
        };
        #[cfg(feature = "logging")]
        println!("Loaded key option {}", k);
        info!("Loaded key option {}", k);
        Ok(ok)
    }
    /// Converts old single-string file options into key-value storage


@@ 101,13 102,17 @@ impl Theme {
    {
        let key = name.into();
        let mut value = String::new();
        info!("Opening old key file and reading");
        fs::File::open(get_home() + "/.config/raven/themes/" + &self.name + "/" + &key)?
            .read_to_string(&mut value)?;
        info!("Loading current theme store");
        let mut store = load_store(self.name.clone())?;
        info!("Inserting key and value into key-value store");
        store.kv.insert(
            key.clone(),
            serde_json::Value::String(value.clone().trim().to_string()),
        );
        info!("Removing old option");
        store.options = store
            .options
            .iter()


@@ 115,8 120,8 @@ impl Theme {
            .map(|x| x.to_owned())
            .collect();
        up_theme(store)?;
        #[cfg(feature = "logging")]
        println!("Converted option {} to new key-value system", key);
        info!("Converted option {} to new key-value system", key);
        info!("Loading new key");
        Ok(self.load_k(key, value)?)
    }
    /// Iterates through options and loads them with submethods


@@ 127,8 132,7 @@ impl Theme {
        let len = opt.len();
        while i <= len {
            let ref option = opt[len - i];
            #[cfg(feature = "logging")]
            println!("Loading option {}", option.to_string());
            info!("Loading option {}", option.to_string());
            match option {
                Polybar => self.load_poly(self.monitor).unwrap(),
                OldI3 => self.load_i3(true).unwrap(),


@@ 161,13 165,11 @@ impl Theme {
                    self.convert_single("vscode").unwrap();
                }
            };
            #[cfg(feature = "logging")]
            println!("Loaded option {}", option.to_string());
            info!("Loaded option {}", option.to_string());
            i += 1;
        }
        self.load_kv();
        #[cfg(feature = "logging")]
        println!("Loaded all options for theme {}", self.name);
        info!("Loaded all options for theme {}", self.name);
        Ok(())
    }
    /// Edits the value of a key in hjson files


@@ 177,6 179,7 @@ impl Theme {
        pat: impl Into<String>,
        value: impl Into<String>,
    ) -> Result<()> {
        info!("Editing hjson file");
        let file = &file.into();
        let pat = &pat.into();
        let value = &value.into();


@@ 220,6 223,7 @@ impl Theme {
        if fs::metadata(get_home() + "/.config/rofi").is_err() {
            fs::create_dir(get_home() + "/.config/rofi")?;
        }
        info!("Copying rofi theme to rofi config");
        fs::copy(
            get_home() + "/.config/raven/themes/" + &self.name + "/rofi",
            get_home() + "/.config/rofi/theme.rasi",


@@ 228,10 232,12 @@ impl Theme {
    }
    pub fn load_pywal(&self) -> Result<()> {
        let arg = get_home() + "/.config/raven/themes/" + &self.name + "/pywal";
        info!("Starting wal");
        Command::new("wal").arg("-n").arg("-i").arg(arg).output()?;
        Ok(())
    }
    pub fn load_script(&self) -> Result<()> {
        info!("Starting script");
        Command::new("sh")
            .arg("-c")
            .arg(get_home() + "/.config/raven/themes/" + &self.name + "/script")


@@ 242,22 248,28 @@ impl Theme {
    pub fn load_openbox(&self) -> Result<()> {
        let mut base = String::new();
        if fs::metadata(get_home() + "/.config/raven/base_rc.xml").is_ok() {
            info!("Opening and reading base_rc");
            fs::File::open(get_home() + "/.config/raven/base_rc.xml")?.read_to_string(&mut base)?;
        }
        let mut rest = String::new();
        info!("Opening and reading openbox config");
        fs::File::open(get_home() + "/.config/raven/themes/" + &self.name + "/openbox")?
            .read_to_string(&mut rest)?;
        base.push_str(&rest);
        info!("Removing old openbox config");
        fs::remove_file(get_home() + "/.config/openbox/rc.xml")?;
        info!("Creating and writing to new openbox config");
        OpenOptions::new()
            .create(true)
            .write(true)
            .open(get_home() + "/.config/openbox/rc.xml")?
            .write_all(base.as_bytes())?;
        info!("Starting openbox reload command");
        Command::new("openbox").arg("--reconfigure").output()?;
        Ok(())
    }
    pub fn load_ranger(&self) -> Result<()> {
        info!("Copying ranger config to ranger directory");
        fs::copy(
            get_home() + "/.config/raven/themes/" + &self.name + "/ranger",
            get_home() + "/.config/ranger/rc.conf",


@@ 268,19 280,24 @@ impl Theme {
    pub fn load_dunst(&self) -> Result<()> {
        let mut config = String::new();
        if fs::metadata(get_home() + "/.config/raven/base_dunst").is_ok() {
            info!("Opening and reading base dunst file");
            fs::File::open(get_home() + "/.config/raven/base_dunst")?
                .read_to_string(&mut config)?;
        }
        let mut app = String::new();
        info!("Opening and reading dunst file");
        fs::File::open(get_home() + "/.config/raven/themes/" + &self.name + "/dunst")?
            .read_to_string(&mut app)?;
        config.push_str(&app);
        info!("Removing old dunstrc");
        fs::remove_file(get_home() + "/.config/dunst/dunstrc")?;
        info!("Creating and writing to new dunstrc");
        OpenOptions::new()
            .create(true)
            .write(true)
            .open(get_home() + "/.config/dunst/dunstrc")?
            .write_all(config.as_bytes())?;
        info!("Starting dunst");
        Command::new("dunst").spawn()?;
        Ok(())
    }


@@ 291,8 308,7 @@ impl Theme {
        let path1 = get_home() + "/.config/Code/User";
        let path2 = get_home() + "/.config/Code - OSS/User";
        if fs::metadata(&path1).is_err() && fs::metadata(&path2).is_err() {
            #[cfg(feature = "logging")]
            println!(
            error!(
                "Couldn't find neither .config/Code nor .config/Code - OSS. Do you have VSCode installed? \
                Skipping."
            );


@@ 301,9 317,11 @@ impl Theme {
        let pattern = "\"workbench.colorTheme\": ";
        let value = value.into();
        if fs::metadata(&path1).is_ok() {
            info!("Editing ~/.config/Code/User sublime settings");
            self.edit_hjson(path1 + "/settings.json", pattern, value.as_str())?;
        }
        if fs::metadata(&path2).is_ok() {
            info!("Editing ~/.config/Code - OSS/User sublime settings");
            self.edit_hjson(path2 + "/settings.json", pattern, value)?;
        }
        Ok(true)


@@ 312,8 330,7 @@ impl Theme {
        let stype = &stype.into();
        let path = get_home() + "/.config/sublime-text-3/Packages/User";
        if fs::metadata(&path).is_err() {
            #[cfg(feature = "logging")]
            println!(
            error!(
                "Couldn't find {}. Do you have sublime text 3 installed? \
                 Skipping.",
                &path


@@ 324,6 341,7 @@ impl Theme {
        let mut value = value.into();
        if value.starts_with("sublt/") {
            value = value.trim_start_matches("sublt/").to_string();
            info!("Copying file {}", value);
            fs::copy(
                get_home() + "/.config/raven/themes/" + &self.name + "/sublt/" + &value,
                path.clone() + "/" + &value,


@@ 336,24 354,26 @@ impl Theme {
        } else if stype == "st_subltheme" {
            pattern = "\"theme\": ";
        }
        info!("Editing sublime preferences");
        self.edit_hjson(path + "/Preferences.sublime-settings", pattern, value)?;
        Ok(true)
    }

    pub fn load_ncm(&self) -> Result<bool> {
        if fs::metadata(get_home() + "/.config/ncmpcpp").is_ok() {
            info!("Copying ncmpcpp config to ~/.config/ncmpcpp");
            fs::copy(
                get_home() + "/.config/raven/themes/" + &self.name + "/ncmpcpp",
                get_home() + "/.config/ncmpcpp/config",
            )?;
        } else if fs::metadata(get_home() + "/.ncmpcpp").is_ok() {
            info!("Copying ncmpcpp config to ~/.ncmpcpp");
            fs::copy(
                get_home() + "/.config/raven/themes/" + &self.name + "/ncmpcpp",
                get_home() + "/.ncmpcpp/config",
            )?;
        } else {
            #[cfg(feature = "logging")]
            println!(
            error!(
                "Couldn't detect a ncmpcpp config directory in ~/.config/ncmppcp or ~/.ncmpcpp."
            );
            return Ok(false);


@@ 363,20 383,25 @@ impl Theme {
    pub fn load_bspwm(&self) -> Result<()> {
        let mut config = String::new();
        if fs::metadata(get_home() + "/.config/raven/base_bspwm").is_ok() {
            info!("Opening and reading base bspwm file");
            fs::File::open(get_home() + "/.config/raven/base_bspwm")?
                .read_to_string(&mut config)?;
        }
        let mut app = String::new();
        info!("Opening and reading bspwm config");
        fs::File::open(get_home() + "/.config/raven/themes/" + &self.name + "/bspwm")?
            .read_to_string(&mut app)?;
        config.push_str(&app);
        info!("Removing old bspwmrc");
        fs::remove_file(get_home() + "/.config/bspwm/bspwmrc")?;
        info!("Creating and writing to new bspwmrc");
        OpenOptions::new()
            .create(true)
            .write(true)
            .mode(0o744)
            .open(get_home() + "/.config/bspwm/bspwmrc")?
            .write_all(config.as_bytes())?;
        info!("Starting bspwmrc");
        Command::new("sh")
            .arg("-c")
            .arg(get_home() + "/.config/bspwm/bspwmrc")


@@ 386,36 411,45 @@ impl Theme {
    pub fn load_i3(&self, isw: bool) -> Result<()> {
        let mut config = String::new();
        if fs::metadata(get_home() + "/.config/raven/base_i3").is_ok() {
            info!("Opening and reading base i3 config");
            fs::File::open(get_home() + "/.config/raven/base_i3")?.read_to_string(&mut config)?;
        }
        let mut app = String::new();
        if isw {
            info!("Loading and reading old-style i3 config");
            fs::File::open(get_home() + "/.config/raven/themes/" + &self.name + "/wm")?
                .read_to_string(&mut app)?;
        } else {
            info!("Loading and reading i3 config");
            fs::File::open(get_home() + "/.config/raven/themes/" + &self.name + "/i3")?
                .read_to_string(&mut app)?;
        }
        config.push_str(&app);
        if fs::metadata(get_home() + "/.config/i3").is_err() {
            info!("Creating dir ~/.config/i3");
            fs::create_dir(get_home() + "/.config/i3")?;
        }
        if fs::metadata(get_home() + "/.config/i3/config").is_ok() {
            info!("Removing old i3 config");
            fs::remove_file(get_home() + "/.config/i3/config")?;
        }
        info!("Creating and writing to i3 config");
        OpenOptions::new()
            .create(true)
            .write(true)
            .open(get_home() + "/.config/i3/config")?
            .write_all(config.as_bytes())?;
        info!("Starting command to reload i3");
        Command::new("i3-msg").arg("reload").output()?;
        Ok(())
    }
    pub fn load_termite(&self) -> Result<()> {
        info!("Copying termite config to termite dir");
        fs::copy(
            get_home() + "/.config/raven/themes/" + &self.name + "/termite",
            get_home() + "/.config/termite/config",
        )?;
        info!("Sending SIGUSR1 to termite processes");
        Command::new("pkill")
            .arg("-SIGUSR1")
            .arg("termite")


@@ 424,6 458,7 @@ impl Theme {
    }
    pub fn load_poly(&self, monitor: i32) -> Result<()> {
        for number in 0..monitor {
            info!("Starting polybar for monitor #{}", number);
            Command::new("sh")
                .arg("-c")
                .arg(


@@ 440,12 475,14 @@ impl Theme {
        Ok(())
    }
    fn load_lemon(&self) -> Result<()> {
        info!("Starting lemonbar script");
        Command::new("sh")
            .arg(get_home() + "/.config/raven/themes/" + &self.name + "/lemonbar")
            .spawn()?;
        Ok(())
    }
    fn load_wall(&self) -> Result<()> {
        info!("Starting feh to load wallpaper");
        Command::new("feh")
            .arg("--bg-scale")
            .arg(get_home() + "/.config/raven/themes/" + &self.name + "/wall")


@@ 459,6 496,7 @@ impl Theme {
            name.push_str("_m");
            xres.arg("-merge");
        }
        info!("Loading xresources file");
        xres.arg(get_home() + "/.config/raven/themes/" + &self.name + "/" + &name)
            .output()?;
        Ok(())


@@ 475,15 513,16 @@ where
        let mut conf = get_config()?;
        conf.editing = theme_name.to_string();
        up_config(conf)?;
        #[cfg(feature = "logging")]
        println!("You are now editing the theme {}", &theme_name);
        Ok(theme_name)
    } else {
        error!("Theme does not exist!");
        Err(ErrorKind::InvalidThemeName(theme_name).into())
    }
}
/// Clears possible remnants of old themes
pub fn clear_prev() -> Result<()> {
    info!("Killing polybar, lemonbar, and dunst");
    Command::new("pkill").arg("polybar").output()?;
    Command::new("pkill").arg("lemonbar").output()?;
    Command::new("pkill").arg("dunst").output()?;


@@ 494,6 533,7 @@ pub fn del_theme<N>(theme_name: N) -> Result<()>
where
    N: Into<String>,
{
    info!("Removing theme directory");
    fs::remove_dir_all(get_home() + "/.config/raven/themes/" + &theme_name.into())?;
    Ok(())
}


@@ 504,11 544,11 @@ where
{
    let last = last.into();
    if last.chars().count() > 0 {
        info!("Running last loaded theme");
        run_theme(&load_theme(last.trim())?)?;
        Ok(())
    } else {
        #[cfg(feature = "logging")]
        println!("No last theme saved. Cannot refresh.");
        error!("No last theme saved. Cannot refresh.");
        Err(ErrorKind::InvalidThemeName(last).into())
    }
}


@@ 518,7 558,9 @@ where
    N: Into<String>,
{
    let theme_name = theme_name.into();
    info!("Creating theme dir");
    fs::create_dir(get_home() + "/.config/raven/themes/" + &theme_name)?;
    info!("Creating theme store file");
    let mut file = OpenOptions::new()
        .create(true)
        .write(true)


@@ 532,6 574,7 @@ where
        kv: Map::new(),
    };
    let st = serde_json::to_string(&stdef)?;
    info!("Writing to theme store");
    file.write_all(st.as_bytes())?;
    edit(theme_name)?;
    Ok(())


@@ 543,6 586,7 @@ pub fn add_to_theme(
    path: impl Into<String>,
) -> Result<()> {
    let (theme_name, option, path) = (theme_name.into(), option.into(), path.into());
    info!("Loading theme");
    let cur_theme = load_theme(theme_name.as_str())?;
    let cur_st = load_store(theme_name.as_str())?;
    let opts = cur_theme.options.iter().map(|x| x.to_string()).collect();


@@ 561,11 605,13 @@ pub fn add_to_theme(
        }
    }
    if !already_used {
        info!("Adding new option to theme. Updating theme store.");
        new_themes.options.push(option.clone());
        up_theme(new_themes)?;
    }
    let mut totpath = env::current_dir()?;
    totpath.push(path);
    info!("Copying option {} to theme directory", option);
    fs::copy(
        totpath,
        get_home() + "/.config/raven/themes/" + &theme_name + "/" + &option,


@@ 575,7 621,9 @@ pub fn add_to_theme(
/// Remove an option from a theme
pub fn rm_from_theme(theme_name: impl Into<String>, option: impl Into<String>) -> Result<()> {
    let (theme_name, option) = (theme_name.into(), option.into());
    info!("Loading theme");
    let cur_theme = load_theme(theme_name.as_str())?;
    info!("Loading store");
    let cur_st = load_store(theme_name.as_str())?;
    let opts = cur_theme.options.iter().map(|x| x.to_string()).collect();
    let mut new_themes = ThemeStore {


@@ 590,25 638,25 @@ pub fn rm_from_theme(theme_name: impl Into<String>, option: impl Into<String>) -
    let mut i = 0;
    while i < new_themes.options.len() {
        if &new_themes.options[i] == &option {
            #[cfg(feature = "logging")]
            println!("Found option {}", option);
            info!("Found option {}. Removing.", option);
            found = true;
            new_themes.options.remove(i);
        }
        i += 1;
    }
    if found {
        info!("Updating theme store.");
        up_theme(new_themes)?;
        Ok(())
    } else {
        #[cfg(feature = "logging")]
        println!("Couldn't find option {}", option);
        error!("Couldn't find option {}", option);
        Err(ErrorKind::InvalidThemeName(theme_name).into())
    }
}
/// Run/refresh a loaded Theme
pub fn run_theme(new_theme: &Theme) -> Result<()> {
    clear_prev()?;
    info!("Running theme options");
    new_theme.load_all()?;
    // Updates the 'last loaded theme' information for later use by raven refresh
    let mut conf = get_config()?;


@@ 618,6 666,7 @@ pub fn run_theme(new_theme: &Theme) -> Result<()> {
}
/// Get all themes
pub fn get_themes() -> Result<Vec<String>> {
    info!("Reading in all themes");
    Ok(fs::read_dir(get_home() + "/.config/raven/themes")?
        .collect::<Vec<io::Result<DirEntry>>>()
        .into_iter()


@@ 631,6 680,7 @@ pub fn key_value(
    theme: impl Into<String>,
) -> Result<()> {
    let mut store = load_store(theme.into())?;
    info!("Inserting new key-value into store");
    store
        .kv
        .insert(key.into(), serde_json::Value::String(value.into()));


@@ 643,15 693,16 @@ where
    N: Into<String>,
{
    let theme_name = theme_name.into();

    info!("Loading config");
    let conf = get_config()?;
    info!("Loading theme directory");
    let ent_res = fs::read_dir(get_home() + "/.config/raven/themes/" + &theme_name);
    if ent_res.is_ok() {
        #[cfg(feature = "logging")]
        println!("Found theme {}", theme_name);
        info!("Found theme {}", theme_name);
        if fs::metadata(get_home() + "/.config/raven/themes/" + &theme_name + "/theme.json").is_ok()
        {
            let theme_info = load_store(theme_name.as_str())?;
            info!("Loading options");
            let opts: Vec<ROption> = theme_info
                .options
                .iter()


@@ 673,16 724,17 @@ where
            };
            Ok(new_theme)
        } else {
            error!("Theme store does not exist");
            Err(ErrorKind::InvalidThemeName(theme_name).into())
        }
    } else {
        #[cfg(feature = "logging")]
        println!("Theme does not exist.");
        error!("Theme does not exist.");
        Err(ErrorKind::InvalidThemeName(theme_name).into())
    }
}
/// Loads all themes
pub fn load_themes() -> Result<Vec<Theme>> {
    info!("Loading all themes");
    Ok(get_themes()?
        .iter()
        .map(|x| load_theme(x.as_str()))