fpm_core/
db.rs

1use std::collections::BTreeMap;
2use std::collections::HashSet;
3use std::env;
4use std::fs;
5use std::mem;
6use std::path;
7use std::time::Instant;
8
9use crate::module::SoftwareModule;
10use flatpak_rs::module::FlatpakModule;
11
12pub const MODULES_DB_SUBDIR: &str = "/modules";
13
14pub struct Database {
15    pub modules: Vec<SoftwareModule>,
16}
17impl Database {
18    pub fn get_database() -> Database {
19        if let Err(e) = fs::create_dir_all(Database::get_modules_db_path()) {
20            panic!("Could not initialize database directory: {}.", e);
21        }
22
23        let before_loading = Instant::now();
24        let database = Database {
25            modules: Database::get_all_modules(),
26        };
27        let loading_duration = before_loading.elapsed();
28        if loading_duration.as_secs() == 0 {
29            log::info!("Loading the database took {}ms.", loading_duration.as_millis());
30        } else {
31            log::info!("Loading the database took {}s.", loading_duration.as_secs());
32        }
33
34        database
35    }
36
37    pub fn get_stats(&self) -> String {
38        let mut response = "".to_string();
39        response += &format!("Modules: {}.\n", self.modules.len());
40
41        let mut updateable_module_count = 0;
42        let mut module_build_systems_count: BTreeMap<String, i64> = BTreeMap::new();
43
44        for module in &self.modules {
45            if module.flatpak_module.uses_external_data_checker() {
46                updateable_module_count += 1;
47            }
48            if let Some(build_system) = &module.flatpak_module.buildsystem {
49                let build_system_name = build_system.to_string();
50                let new_build_system_count =
51                    module_build_systems_count.get(&build_system_name).unwrap_or(&0) + 1;
52                module_build_systems_count.insert(build_system_name.to_string(), new_build_system_count);
53            }
54        }
55        response += &format!("Modules supporting updates: {}.\n", updateable_module_count);
56
57        response += &format!(
58            "Database in-memory size: {}.\n",
59            crate::utils::format_bytes(self.get_database_memory_size())
60        );
61
62        for (build_system, build_system_count) in module_build_systems_count {
63            response += &format!(
64                "{:05.2}% Modules use {}\n",
65                (build_system_count as f64 / self.modules.len() as f64) * 100.0,
66                build_system,
67            );
68        }
69
70        // TODO print module type stats.
71        // TODO print archive type stats.
72        // TODO print domain (URL domain) stats.
73        // TODO print domain (URL domain) stats for the main vcs repo.
74        // TODO add the number of archive urls.
75
76        response
77    }
78
79    pub fn get_database_memory_size(&self) -> usize {
80        let mut db_size = 0;
81        for module in &self.modules {
82            db_size += mem::size_of_val(module);
83        }
84        return db_size;
85    }
86
87    pub fn get_db_path() -> String {
88        let default_db_path: String = match env::var("HOME") {
89            Ok(h) => format!("{}/.fpm-db", h),
90            Err(_e) => ".fpm-db".to_string(),
91        };
92
93        let db_path = match env::var("FPM_DB_DIR") {
94            Ok(p) => p,
95            Err(_e) => {
96                log::debug!("FPM_DB_DIR is not defined. Defaulting to {}.", default_db_path);
97                return default_db_path;
98            }
99        };
100        if let Err(e) = fs::create_dir_all(&db_path) {
101            panic!("Could not initialize DB directory: {}.", e);
102        }
103        db_path
104    }
105
106    pub fn get_modules_db_path() -> String {
107        Database::get_db_path() + MODULES_DB_SUBDIR
108    }
109
110    pub fn get_all_modules() -> Vec<SoftwareModule> {
111        let modules_path = Database::get_modules_db_path();
112        let modules_path = path::Path::new(&modules_path);
113        let all_modules_paths = match crate::utils::get_all_paths(modules_path) {
114            Ok(paths) => paths,
115            Err(e) => {
116                log::error!("Could not get modules from database: {}.", e);
117                return vec![];
118            }
119        };
120        let mut modules: Vec<SoftwareModule> = vec![];
121        for module_path in all_modules_paths.iter() {
122            let module_path_str = module_path.to_str().unwrap();
123            if !module_path.is_file() {
124                log::debug!("{} is not a file.", &module_path_str);
125                continue;
126            }
127            // Don't even try to open it if it's not a yaml file.
128            if !module_path_str.ends_with("yml") && !module_path_str.ends_with("yaml") {
129                continue;
130            }
131            let module_content = match fs::read_to_string(module_path) {
132                Ok(content) => content,
133                Err(e) => {
134                    log::debug!("Could not read module file {}: {}.", &module_path_str, e);
135                    continue;
136                }
137            };
138            let module = match serde_yaml::from_str(&module_content) {
139                Ok(m) => m,
140                Err(e) => {
141                    log::debug!("Could not parse module file at {}: {}.", &module_path_str, e);
142                    continue;
143                }
144            };
145            modules.push(module);
146        }
147        modules
148    }
149
150    pub fn search_modules(&self, search_term: &str) -> Vec<&FlatpakModule> {
151        let mut modules: Vec<&FlatpakModule> = vec![];
152        for module in &self.modules {
153            if module
154                .flatpak_module
155                .name
156                .to_lowercase()
157                .contains(&search_term.to_lowercase())
158            {
159                modules.push(&module.flatpak_module);
160            }
161        }
162        modules
163    }
164
165    pub fn remove_module() {}
166
167    pub fn add_module(&mut self, new_module: FlatpakModule) {
168        let module_hash = crate::utils::get_module_hash(&new_module);
169        let mut new_software_module = SoftwareModule::default();
170        new_software_module.flatpak_module = new_module;
171
172        let modules_path = Database::get_modules_db_path();
173        let new_module_path = format!("{}/{}.yaml", modules_path, module_hash);
174        log::info!("Adding module at {}", new_module_path);
175        let new_module_fs_path = path::Path::new(&new_module_path);
176        if new_module_fs_path.exists() {
177            // The path is based on a hash of the module, so there should be no need to
178            // update a file that exists.
179            return;
180        }
181        match fs::write(
182            new_module_fs_path,
183            serde_yaml::to_string(&new_software_module).unwrap(),
184        ) {
185            Ok(content) => content,
186            Err(e) => {
187                eprintln!(
188                    "Could not write new module at {}: {}",
189                    new_module_path.to_string(),
190                    e
191                );
192            }
193        };
194        self.modules.push(new_software_module);
195    }
196}