xbp_cli/codetime/
cursor.rs1use 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}