Use XDG-compliant directories for config and cache files

Antonio Scandurra created

Change summary

crates/project/src/db.rs | 10 ++++----
crates/zed/src/main.rs   | 48 +++++++++++++----------------------------
crates/zed/src/zed.rs    | 26 +++++++++++++++-------
3 files changed, 38 insertions(+), 46 deletions(-)

Detailed changes

crates/project/src/db.rs 🔗

@@ -1,5 +1,5 @@
 use anyhow::Result;
-use std::path::PathBuf;
+use std::path::Path;
 use std::sync::Arc;
 
 pub struct Db(DbStore);
@@ -16,8 +16,8 @@ enum DbStore {
 
 impl Db {
     /// Open or create a database at the given file path.
-    pub fn open(path: PathBuf) -> Result<Arc<Self>> {
-        let db = rocksdb::DB::open_default(&path)?;
+    pub fn open(path: &Path) -> Result<Arc<Self>> {
+        let db = rocksdb::DB::open_default(path)?;
         Ok(Arc::new(Self(DbStore::Real(db))))
     }
 
@@ -125,7 +125,7 @@ mod tests {
     fn test_db() {
         let dir = TempDir::new("db-test").unwrap();
         let fake_db = Db::open_fake();
-        let real_db = Db::open(dir.path().join("test.db")).unwrap();
+        let real_db = Db::open(&dir.path().join("test.db")).unwrap();
 
         for db in [&real_db, &fake_db] {
             assert_eq!(
@@ -152,7 +152,7 @@ mod tests {
 
         drop(real_db);
 
-        let real_db = Db::open(dir.path().join("test.db")).unwrap();
+        let real_db = Db::open(&dir.path().join("test.db")).unwrap();
         assert_eq!(
             real_db.read(["key-1", "key-2", "key-3"]).unwrap(),
             &[Some("one".as_bytes().to_vec()), None, None,]

crates/zed/src/main.rs 🔗

@@ -28,15 +28,7 @@ use project::{Fs, ProjectStore};
 use serde_json::json;
 use settings::{self, KeymapFileContent, Settings, SettingsFileContent};
 use smol::process::Command;
-use std::{
-    env,
-    ffi::OsStr,
-    fs, panic,
-    path::{Path, PathBuf},
-    sync::Arc,
-    thread,
-    time::Duration,
-};
+use std::{env, ffi::OsStr, fs, panic, path::PathBuf, sync::Arc, thread, time::Duration};
 use terminal;
 use theme::ThemeRegistry;
 use util::{ResultExt, TryFutureExt};
@@ -50,21 +42,19 @@ use zed::{
 
 fn main() {
     let http = http::client();
-    let home_dir = dirs::home_dir().expect("could not find home dir");
-    let db_dir_path = home_dir.join("Library/Application Support/Zed");
-    let logs_dir_path = home_dir.join("Library/Logs/Zed");
-    fs::create_dir_all(&db_dir_path).expect("could not create database path");
-    fs::create_dir_all(&logs_dir_path).expect("could not create logs path");
-    init_logger(&logs_dir_path);
+    fs::create_dir_all(&*zed::LANGUAGES_DIR_PATH).expect("could not create languages path");
+    fs::create_dir_all(&*zed::DB_DIR_PATH).expect("could not create database path");
+    fs::create_dir_all(&*zed::LOGS_DIR_PATH).expect("could not create logs path");
+    init_logger();
 
     log::info!("========== starting zed ==========");
     let mut app = gpui::App::new(Assets).unwrap();
     let app_version = ZED_APP_VERSION
         .or_else(|| app.platform().app_version().ok())
         .map_or("dev".to_string(), |v| v.to_string());
-    init_panic_hook(logs_dir_path, app_version, http.clone(), app.background());
+    init_panic_hook(app_version, http.clone(), app.background());
     let db = app.background().spawn(async move {
-        project::Db::open(db_dir_path.join("zed.db"))
+        project::Db::open(&*zed::DB_PATH)
             .log_err()
             .unwrap_or(project::Db::null())
     });
@@ -100,7 +90,7 @@ fn main() {
     app.run(move |cx| {
         let client = client::Client::new(http.clone());
         let mut languages = LanguageRegistry::new(login_shell_env_loaded);
-        languages.set_language_server_download_dir(zed::ROOT_PATH.clone());
+        languages.set_language_server_download_dir(zed::LANGUAGES_DIR_PATH.clone());
         let languages = Arc::new(languages);
         let init_languages = cx
             .background()
@@ -205,42 +195,34 @@ fn main() {
     });
 }
 
-fn init_logger(logs_dir_path: &Path) {
+fn init_logger() {
     if stdout_is_a_pty() {
         env_logger::init();
     } else {
         let level = LevelFilter::Info;
-        let log_file_path = logs_dir_path.join("Zed.log");
 
         // Prevent log file from becoming too large.
         const MAX_LOG_BYTES: u64 = 1 * 1024 * 1024;
-        if fs::metadata(&log_file_path).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES) {
-            let _ = fs::rename(&log_file_path, logs_dir_path.join("Zed.log.old"));
+        if fs::metadata(&*zed::LOG_PATH).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES) {
+            let _ = fs::rename(&*zed::LOG_PATH, &*zed::OLD_LOG_PATH);
         }
 
         let log_file = OpenOptions::new()
             .create(true)
             .append(true)
-            .open(log_file_path)
+            .open(&*zed::LOG_PATH)
             .expect("could not open logfile");
         simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file)
             .expect("could not initialize logger");
     }
 }
 
-fn init_panic_hook(
-    logs_dir_path: PathBuf,
-    app_version: String,
-    http: Arc<dyn HttpClient>,
-    background: Arc<Background>,
-) {
+fn init_panic_hook(app_version: String, http: Arc<dyn HttpClient>, background: Arc<Background>) {
     background
         .spawn({
-            let logs_dir_path = logs_dir_path.clone();
-
             async move {
                 let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL);
-                let mut children = smol::fs::read_dir(&logs_dir_path).await?;
+                let mut children = smol::fs::read_dir(&*zed::LOGS_DIR_PATH).await?;
                 while let Some(child) = children.next().await {
                     let child = child?;
                     let child_path = child.path();
@@ -330,7 +312,7 @@ fn init_panic_hook(
 
         let panic_filename = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
         fs::write(
-            logs_dir_path.join(format!("zed-{}-{}.panic", app_version, panic_filename)),
+            zed::LOGS_DIR_PATH.join(format!("zed-{}-{}.panic", app_version, panic_filename)),
             &message,
         )
         .context("error writing panic to disk")

crates/zed/src/zed.rs 🔗

@@ -31,6 +31,7 @@ use serde::Deserialize;
 use serde_json::to_string_pretty;
 use settings::{keymap_file_json_schema, settings_file_json_schema, Settings};
 use std::{
+    env,
     path::{Path, PathBuf},
     str,
     sync::Arc,
@@ -67,13 +68,22 @@ actions!(
 const MIN_FONT_SIZE: f32 = 6.0;
 
 lazy_static! {
-    pub static ref HOME_PATH: PathBuf =
-        dirs::home_dir().expect("failed to determine home directory");
-    pub static ref LOG_PATH: PathBuf = HOME_PATH.join("Library/Logs/Zed/Zed.log");
-    pub static ref OLD_LOG_PATH: PathBuf = HOME_PATH.join("Library/Logs/Zed/Zed.log.old");
-    pub static ref ROOT_PATH: PathBuf = HOME_PATH.join(".zed");
-    pub static ref SETTINGS_PATH: PathBuf = ROOT_PATH.join("settings.json");
-    pub static ref KEYMAP_PATH: PathBuf = ROOT_PATH.join("keymap.json");
+    static ref HOME_PATH: PathBuf = dirs::home_dir().expect("failed to determine home directory");
+    static ref CACHE_DIR_PATH: PathBuf = dirs::cache_dir()
+        .expect("failed to determine cache directory")
+        .join("Zed");
+    static ref CONFIG_DIR_PATH: PathBuf = env::var_os("XDG_CONFIG_HOME")
+        .map(|home| home.into())
+        .unwrap_or_else(|| HOME_PATH.join(".config"))
+        .join("zed");
+    pub static ref LOGS_DIR_PATH: PathBuf = HOME_PATH.join("Library/Logs/Zed");
+    pub static ref LANGUAGES_DIR_PATH: PathBuf = CACHE_DIR_PATH.join("languages");
+    pub static ref DB_DIR_PATH: PathBuf = CACHE_DIR_PATH.join("db");
+    pub static ref DB_PATH: PathBuf = DB_DIR_PATH.join("zed.db");
+    pub static ref SETTINGS_PATH: PathBuf = CONFIG_DIR_PATH.join("settings.json");
+    pub static ref KEYMAP_PATH: PathBuf = CONFIG_DIR_PATH.join("keymap.json");
+    pub static ref LOG_PATH: PathBuf = LOGS_DIR_PATH.join("Zed.log");
+    pub static ref OLD_LOG_PATH: PathBuf = LOGS_DIR_PATH.join("Zed.log.old");
 }
 
 pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
@@ -399,7 +409,7 @@ fn open_config_file(
     cx.spawn(|workspace, mut cx| async move {
         let fs = &app_state.fs;
         if !fs.is_file(path).await {
-            fs.create_dir(&ROOT_PATH).await?;
+            fs.create_dir(&CONFIG_DIR_PATH).await?;
             fs.create_file(path, Default::default()).await?;
             fs.save(path, &default_content(), Default::default())
                 .await?;