1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Module for interacting with `config.toml` and `passwords.toml`.

use crate::files::*;
use crate::generator::Generator;

use serde_derive::Serialize;
use std::collections::HashMap;
use std::io::Read;
use std::io::Write;

/// Struct for serializing passwords' file structure data.
#[derive(Serialize)]
pub struct PasswordsFile(HashMap<String, HashMap<String, String>>);

/// `config.toml` and `passwords.toml` files' content.
pub struct FileContent {
    config_file: fs::File,
    password_file: fs::File,
}

impl FileContent {
    // todo: proper handling of errors, not via 'expect'
    /// Open and store files in a struct.
    pub fn default() -> Self {
        let paths_list = FilePaths::default();
        let config_file = fs::OpenOptions::new()
            .read(true)
            .write(true)
            .open(paths_list.config_path)
            .expect("could not open");
        let password_file = fs::OpenOptions::new()
            .read(true)
            .write(true)
            .open(paths_list.password_path)
            .expect("could not open");
        Self {
            config_file,
            password_file,
        }
    }

    /// Initialize default config if file is empty (we are always reading config from file).
    pub fn init_config(mut self) -> Self {
        let mut config_content: String = String::new();
        self.config_file
            .read_to_string(&mut config_content)
            .expect("failed to make a string");
        if config_content.is_empty() {
            let config_struct: Generator = Generator::new(10)
                .lowercase(true)
                .uppercase(true)
                .symbols(true)
                .numbers(true)
                .begin_with_letter(true)
                .category("unknown".to_string());

            config_content = toml::to_string(&config_struct).unwrap();
            let config_byte_content = config_content.as_bytes();

            self.config_file
                .write_all(config_byte_content)
                .expect("failed to write to file");
        }

        self
    }

    /// Load and edit `passwords.toml` file's content.
    /// *NOTE*: this function does not save content, only returns modified string.
    pub fn edit_passwords(mut self, category_name: Option<&str>, password_name: String) -> String {
        let mut config_content: String = String::new();
        let mut passwords_content: String = String::new();

        self.config_file
            .read_to_string(&mut config_content)
            .expect("failed to make a string");

        self.password_file
            .read_to_string(&mut passwords_content)
            .expect("failed to make a string");

        let generator: Generator =
            toml::from_str(&config_content).expect("failed to generate config");
        let password = generator.generate().unwrap();

        if passwords_content.is_empty() {
            let mut passwords_map: HashMap<String, String> = HashMap::new();
            passwords_map.insert(password_name, password);

            let mut categories_map: HashMap<String, HashMap<String, String>> = HashMap::new();

            if let Some(category_value) = category_name {
                categories_map.insert(category_value.to_string(), passwords_map);
            } else {
                categories_map.insert(generator.category, passwords_map);
            }

            let file_content = PasswordsFile(categories_map);
            toml::to_string_pretty(&file_content).expect("failed to generate string")
        } else {
            let mut file_content: HashMap<String, HashMap<String, String>> =
                toml::from_str(&passwords_content).expect("failed to convert");

            if let Some(category_value) = category_name {
                if let Some(category_map) = file_content.get_mut(category_value) {
                    category_map.insert(password_name, password);
                } else {
                    let mut password_map: HashMap<String, String> = HashMap::new();

                    password_map.insert(password_name, password);
                    file_content.insert(category_value.to_string(), password_map);
                }
            } else if let Some(category_map) = file_content.get_mut(&generator.category) {
                category_map.insert(password_name, password);
            } else {
                let mut password_map: HashMap<String, String> = HashMap::new();

                password_map.insert(password_name, password);
                file_content.insert(generator.category, password_map);
            }

            toml::to_string_pretty(&file_content).expect("failed to generate string")
        }
    }

    /// Save modified content to file with given options (category and password name).
    pub fn save_passwords(self, category_name: Option<&str>, password_name: String) {
        // this might be changed: FilesPaths is dropped right after this line
        let path = { FilePaths::default().password_path };
        let write = self.edit_passwords(category_name, password_name);
        let mut file = fs::OpenOptions::new().write(true).open(path).unwrap();
        file.write_all(write.as_bytes()).unwrap();
    }
}