diff --git a/Cargo.lock b/Cargo.lock index 93a276a60..2ad7be305 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -849,6 +849,27 @@ dependencies = [ "generic-array 0.14.4", ] +[[package]] +name = "dirs-next" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "discard" version = "1.0.4" @@ -1653,6 +1674,7 @@ dependencies = [ "parking_lot", "paste", "pin-project", + "platform-dirs", "rand", "rayon", "regex", @@ -2172,6 +2194,15 @@ version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" +[[package]] +name = "platform-dirs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e188d043c1a692985f78b5464853a263f1a27e5bd6322bad3a4078ee3c998a38" +dependencies = [ + "dirs-next", +] + [[package]] name = "ppv-lite86" version = "0.2.10" @@ -2353,6 +2384,16 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + [[package]] name = "regex" version = "1.5.4" diff --git a/meilisearch-http/Cargo.toml b/meilisearch-http/Cargo.toml index ffc660c80..41bae43b8 100644 --- a/meilisearch-http/Cargo.toml +++ b/meilisearch-http/Cargo.toml @@ -51,6 +51,7 @@ mime = "0.3.16" num_cpus = "1.13.0" once_cell = "1.8.0" parking_lot = "0.11.2" +platform-dirs = "0.3.0" rand = "0.8.4" rayon = "1.5.1" regex = "1.5.4" diff --git a/meilisearch-http/src/analytics.rs b/meilisearch-http/src/analytics.rs index 04ac79211..2ff739f65 100644 --- a/meilisearch-http/src/analytics.rs +++ b/meilisearch-http/src/analytics.rs @@ -1,11 +1,50 @@ -use actix_web::HttpRequest; -use meilisearch_lib::index::SearchQuery; -use serde_json::Value; -use std::fmt::Display; -use std::fs::read_to_string; - use crate::routes::indexes::documents::UpdateDocumentsQuery; use crate::Opt; +use actix_web::HttpRequest; +use meilisearch_lib::index::SearchQuery; +use once_cell::sync::Lazy; +use platform_dirs::AppDirs; +use serde_json::Value; +use std::fmt::Display; +use std::fs; +use std::path::PathBuf; + +/// The MeiliSearch config dir: +/// `~/.config/MeiliSearch` on *NIX or *BSD. +/// `~/Library/ApplicationSupport` on macOS. +/// `%APPDATA` (= `C:\Users%USERNAME%\AppData\Roaming`) on windows. +static MEILISEARCH_CONFIG_PATH: Lazy> = + Lazy::new(|| AppDirs::new(Some("MeiliSearch"), false).map(|appdir| appdir.config_dir)); + +fn config_user_id_path(opt: &Opt) -> Option { + opt.db_path + .canonicalize() + .ok() + .map(|path| path.join("user-id").display().to_string().replace("/", "-")) + .zip(MEILISEARCH_CONFIG_PATH.as_ref()) + .map(|(filename, config_path)| config_path.join(filename)) +} + +/// Look for the user-id in the `data.ms` or in `~/.config/MeiliSearch/path-to-db-user-id` +fn find_user_id(opt: &Opt) -> Option { + fs::read_to_string(opt.db_path.join("user-id")) + .ok() + .or_else(|| fs::read_to_string(&config_user_id_path(opt)?).ok()) +} + +#[cfg(all(not(debug_assertions), feature = "analytics"))] +/// Write the user-id in the `data.ms` and in `~/.config/MeiliSearch/path-to-db-user-id`. Ignore the errors. +fn write_user_id(opt: &Opt, user_id: &str) { + let _ = fs::write(opt.db_path.join("user-id"), user_id.as_bytes()); + if let Some((meilisearch_config_path, user_id_path)) = MEILISEARCH_CONFIG_PATH + .as_ref() + .zip(config_user_id_path(opt)) + { + println!("{}", user_id_path.display()); + let _ = fs::create_dir_all(&meilisearch_config_path); + let _ = fs::write(user_id_path, user_id.as_bytes()); + } +} // if we are in release mode and the feature analytics was enabled #[cfg(all(not(debug_assertions), feature = "analytics"))] @@ -24,7 +63,6 @@ mod segment { use serde_json::{json, Value}; use std::collections::{HashMap, HashSet}; use std::fmt::Display; - use std::fs; use std::time::{Duration, Instant}; use sysinfo::{DiskExt, System, SystemExt}; use tokio::sync::Mutex; @@ -100,17 +138,12 @@ mod segment { } pub async fn new(opt: &Opt, meilisearch: &MeiliSearch) -> &'static Self { - // see if there is already a user-id in the `data.ms` - let user_id = fs::read_to_string(opt.db_path.join("user-id")) - .or_else(|_| fs::read_to_string("/tmp/meilisearch-user-id")); - let first_time_run = user_id.is_err(); + // see if there is already a user-id in the `data.ms` or in `/tmp/path-to-db-user-id` + let user_id = super::find_user_id(opt); + let first_time_run = user_id.is_none(); // if not, generate a new user-id and save it to the fs - let user_id = user_id.unwrap_or_else(|_| Uuid::new_v4().to_string()); - let _ = fs::write(opt.db_path.join("user-id"), user_id.as_bytes()); - let _ = fs::write( - opt.db_path.join("/tmp/meilisearch-user-id"), - user_id.as_bytes(), - ); + let user_id = user_id.unwrap_or_else(|| Uuid::new_v4().to_string()); + super::write_user_id(opt, &user_id); let client = HttpClient::default(); let user = User::UserId { @@ -536,9 +569,7 @@ pub struct MockAnalytics { impl MockAnalytics { pub fn new(opt: &Opt) -> &'static Self { - let user = read_to_string(opt.db_path.join("user-id")) - .or_else(|_| read_to_string("/tmp/meilisearch-user-id")) - .unwrap_or_else(|_| "".to_string()); + let user = find_user_id(opt).unwrap_or(String::new()); let analytics = Box::new(Self { user }); Box::leak(analytics) }