refactor: centralize path handling with XDG Base Directory compliance

- Add src/paths.rs module for all XDG path lookups
- Move scripts from ~/.config to ~/.local/share (XDG data)
- Use $XDG_CONFIG_HOME for browser bookmark paths
- Add dev-logging feature flag for verbose debug output
- Add dev-install profile for testable release builds
- Remove CLAUDE.md from version control

BREAKING: Scripts directory moved from
~/.config/owlry/scripts/ to ~/.local/share/owlry/scripts/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-29 16:46:14 +01:00
parent 3f7a8950eb
commit 0eccdc5883
16 changed files with 396 additions and 115 deletions

View File

@@ -1,7 +1,7 @@
use super::{LaunchItem, Provider, ProviderType};
use crate::paths;
use freedesktop_desktop_entry::{DesktopEntry, Iter};
use log::{debug, warn};
use std::path::PathBuf;
/// Clean desktop file field codes from command string.
/// Removes %f, %F, %u, %U, %d, %D, %n, %N, %i, %c, %k, %v, %m field codes
@@ -75,25 +75,8 @@ impl ApplicationProvider {
Self { items: Vec::new() }
}
fn get_application_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();
// User applications
if let Some(data_home) = dirs::data_dir() {
dirs.push(data_home.join("applications"));
}
// System applications
dirs.push(PathBuf::from("/usr/share/applications"));
dirs.push(PathBuf::from("/usr/local/share/applications"));
// Flatpak applications
if let Some(data_home) = dirs::data_dir() {
dirs.push(data_home.join("flatpak/exports/share/applications"));
}
dirs.push(PathBuf::from("/var/lib/flatpak/exports/share/applications"));
dirs
fn get_application_dirs() -> Vec<std::path::PathBuf> {
paths::system_data_dirs()
}
}

View File

@@ -1,3 +1,4 @@
use crate::paths;
use crate::providers::{LaunchItem, Provider, ProviderType};
use log::{debug, warn};
use serde::Deserialize;
@@ -27,8 +28,8 @@ impl BookmarksProvider {
fn load_firefox_bookmarks(&mut self) {
// Firefox stores bookmarks in places.sqlite
// The file is locked when Firefox is running, so we read from backup
let firefox_dir = match dirs::home_dir() {
Some(h) => h.join(".mozilla").join("firefox"),
let firefox_dir = match paths::firefox_dir() {
Some(d) => d,
None => return,
};
@@ -99,29 +100,10 @@ impl BookmarksProvider {
}
fn load_chrome_bookmarks(&mut self) {
// Chrome/Chromium bookmarks are in JSON format
let home = match dirs::home_dir() {
Some(h) => h,
None => return,
};
// Try multiple browser paths
let bookmark_paths = [
// Chrome
home.join(".config/google-chrome/Default/Bookmarks"),
// Chromium
home.join(".config/chromium/Default/Bookmarks"),
// Brave
home.join(".config/BraveSoftware/Brave-Browser/Default/Bookmarks"),
// Edge
home.join(".config/microsoft-edge/Default/Bookmarks"),
// Vivaldi
home.join(".config/vivaldi/Default/Bookmarks"),
];
for path in &bookmark_paths {
// Chrome/Chromium bookmarks are in JSON format (XDG config paths)
for path in paths::chromium_bookmark_paths() {
if path.exists() {
self.read_chrome_bookmarks(path);
self.read_chrome_bookmarks(&path);
}
}
}

View File

@@ -1,3 +1,4 @@
use crate::paths;
use crate::providers::{LaunchItem, ProviderType};
use log::{debug, warn};
use std::process::Command;
@@ -106,7 +107,7 @@ impl FileSearchProvider {
fn search_with_fd(&self, pattern: &str) -> Vec<LaunchItem> {
// fd searches from home directory by default
let home = dirs::home_dir().unwrap_or_default();
let home = paths::home().unwrap_or_default();
let output = match Command::new("fd")
.args([
@@ -132,7 +133,7 @@ impl FileSearchProvider {
}
fn search_with_locate(&self, pattern: &str) -> Vec<LaunchItem> {
let home = dirs::home_dir().unwrap_or_default();
let home = paths::home().unwrap_or_default();
let output = match Command::new("locate")
.args([

View File

@@ -30,6 +30,9 @@ use fuzzy_matcher::FuzzyMatcher;
use fuzzy_matcher::skim::SkimMatcherV2;
use log::info;
#[cfg(feature = "dev-logging")]
use log::debug;
use crate::data::FrecencyStore;
/// Represents a single searchable/launchable item
@@ -288,12 +291,16 @@ impl ProviderManager {
frecency: &FrecencyStore,
frecency_weight: f64,
) -> Vec<(LaunchItem, i64)> {
#[cfg(feature = "dev-logging")]
debug!("[Search] query={:?}, max={}, frecency_weight={}", query, max_results, frecency_weight);
let mut results: Vec<(LaunchItem, i64)> = Vec::new();
// Check for calculator query (= or calc prefix)
if CalculatorProvider::is_calculator_query(query) {
if let Some(calc_result) = self.calculator.evaluate(query) {
// Calculator results get a high score to appear first
#[cfg(feature = "dev-logging")]
debug!("[Search] Calculator result: {}", calc_result.name);
results.push((calc_result, 10000));
}
}
@@ -323,6 +330,8 @@ impl ProviderManager {
// Check for file search query
if FileSearchProvider::is_file_query(query) {
let file_results = self.filesearch.evaluate(query);
#[cfg(feature = "dev-logging")]
debug!("[Search] File search returned {} results", file_results.len());
for (idx, item) in file_results.into_iter().enumerate() {
// Score decreases for each result to maintain order
results.push((item, 8000 - idx as i64));
@@ -387,6 +396,18 @@ impl ProviderManager {
results.extend(search_results);
results.sort_by(|a, b| b.1.cmp(&a.1));
results.truncate(max_results);
#[cfg(feature = "dev-logging")]
{
debug!("[Search] Returning {} results", results.len());
for (i, (item, score)) in results.iter().take(5).enumerate() {
debug!("[Search] #{}: {} (score={}, provider={:?})", i + 1, item.name, score, item.provider);
}
if results.len() > 5 {
debug!("[Search] ... and {} more", results.len() - 5);
}
}
results
}

View File

@@ -1,10 +1,11 @@
use crate::paths;
use crate::providers::{LaunchItem, Provider, ProviderType};
use log::{debug, warn};
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
/// Custom scripts provider - runs user scripts from ~/.config/owlry/scripts/
/// Custom scripts provider - runs user scripts from `$XDG_DATA_HOME/owlry/scripts/`
pub struct ScriptsProvider {
items: Vec<LaunchItem>,
}
@@ -14,14 +15,10 @@ impl ScriptsProvider {
Self { items: Vec::new() }
}
fn scripts_dir() -> Option<PathBuf> {
dirs::config_dir().map(|p| p.join("owlry").join("scripts"))
}
fn load_scripts(&mut self) {
self.items.clear();
let scripts_dir = match Self::scripts_dir() {
let scripts_dir = match paths::scripts_dir() {
Some(p) => p,
None => {
debug!("Could not determine scripts directory");
@@ -32,7 +29,7 @@ impl ScriptsProvider {
if !scripts_dir.exists() {
debug!("Scripts directory not found at {:?}", scripts_dir);
// Create the directory for the user
if let Err(e) = fs::create_dir_all(&scripts_dir) {
if let Err(e) = paths::ensure_dir(&scripts_dir) {
warn!("Failed to create scripts directory: {}", e);
}
return;

View File

@@ -1,7 +1,7 @@
use crate::paths;
use crate::providers::{LaunchItem, Provider, ProviderType};
use log::{debug, warn};
use std::fs;
use std::path::PathBuf;
/// SSH connections provider - parses ~/.ssh/config
pub struct SshProvider {
@@ -27,8 +27,8 @@ impl SshProvider {
self.terminal_command = terminal.to_string();
}
fn ssh_config_path() -> Option<PathBuf> {
dirs::home_dir().map(|p| p.join(".ssh").join("config"))
fn ssh_config_path() -> Option<std::path::PathBuf> {
paths::ssh_config()
}
fn parse_ssh_config(&mut self) {