Skip to main content

xbp_cli/codetime/
cursor.rs

1use serde::{Deserialize, Serialize};
2use std::fs;
3use std::path::{Path, PathBuf};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
6pub struct CursorInventory {
7    #[serde(default)]
8    pub supported: bool,
9    #[serde(default)]
10    pub path: String,
11    #[serde(default)]
12    pub exists: bool,
13    #[serde(default)]
14    pub user_dir_exists: bool,
15    #[serde(default)]
16    pub global_storage_exists: bool,
17    #[serde(default)]
18    pub workspace_storage_entries: usize,
19    #[serde(default)]
20    pub extensions_entries: usize,
21    #[serde(default)]
22    pub logs_exists: bool,
23    #[serde(default)]
24    pub note: Option<String>,
25}
26
27pub fn collect_cursor_inventory(explicit_root: Option<&Path>) -> CursorInventory {
28    let root = explicit_root
29        .map(Path::to_path_buf)
30        .or_else(default_cursor_root);
31
32    if !cfg!(windows) {
33        return CursorInventory {
34            supported: false,
35            path: root
36                .as_ref()
37                .map(|path| path.display().to_string())
38                .unwrap_or_default(),
39            exists: root.as_ref().map(|path| path.exists()).unwrap_or(false),
40            user_dir_exists: false,
41            global_storage_exists: false,
42            workspace_storage_entries: 0,
43            extensions_entries: 0,
44            logs_exists: false,
45            note: Some("Cursor scanning is only implemented on Windows right now.".to_string()),
46        };
47    }
48
49    let Some(root) = root else {
50        return CursorInventory {
51            supported: true,
52            path: String::new(),
53            exists: false,
54            user_dir_exists: false,
55            global_storage_exists: false,
56            workspace_storage_entries: 0,
57            extensions_entries: 0,
58            logs_exists: false,
59            note: Some("Unable to resolve %APPDATA%\\Cursor.".to_string()),
60        };
61    };
62
63    let user_dir = root.join("User");
64    let workspace_storage_dir = user_dir.join("workspaceStorage");
65    let global_storage_dir = user_dir.join("globalStorage");
66    let extensions_dir = root.join("extensions");
67    let logs_dir = root.join("logs");
68
69    CursorInventory {
70        supported: true,
71        path: root.display().to_string(),
72        exists: root.exists(),
73        user_dir_exists: user_dir.exists(),
74        global_storage_exists: global_storage_dir.exists(),
75        workspace_storage_entries: count_dir_entries(&workspace_storage_dir),
76        extensions_entries: count_dir_entries(&extensions_dir),
77        logs_exists: logs_dir.exists(),
78        note: None,
79    }
80}
81
82fn default_cursor_root() -> Option<PathBuf> {
83    dirs::data_dir().map(|path| path.join("Cursor"))
84}
85
86fn count_dir_entries(path: &Path) -> usize {
87    fs::read_dir(path)
88        .ok()
89        .into_iter()
90        .flat_map(|entries| entries.flatten())
91        .count()
92}