Rust · 2852 bytes Raw Blame History
1 //! Recent workspaces tracking
2 //!
3 //! Stores recently opened workspaces in ~/.config/fackr/recents.json
4
5 use anyhow::Result;
6 use serde::{Deserialize, Serialize};
7 use std::fs;
8 use std::path::{Path, PathBuf};
9 use std::time::{SystemTime, UNIX_EPOCH};
10
11 #[derive(Debug, Clone, Serialize, Deserialize)]
12 pub struct Recent {
13 pub path: PathBuf,
14 pub label: String,
15 pub last_opened: u64, // Unix timestamp
16 pub open_count: u32,
17 }
18
19 impl Recent {
20 pub fn new(path: PathBuf) -> Self {
21 let label = path
22 .file_name()
23 .map(|s| s.to_string_lossy().to_string())
24 .unwrap_or_else(|| path.to_string_lossy().to_string());
25
26 let timestamp = SystemTime::now()
27 .duration_since(UNIX_EPOCH)
28 .map(|d| d.as_secs())
29 .unwrap_or(0);
30
31 Self {
32 path,
33 label,
34 last_opened: timestamp,
35 open_count: 1,
36 }
37 }
38 }
39
40 /// Get the path to the recents file
41 fn recents_path() -> PathBuf {
42 dirs::config_dir()
43 .unwrap_or_else(|| PathBuf::from("."))
44 .join("fackr")
45 .join("recents.json")
46 }
47
48 /// Load recent workspaces from disk
49 pub fn recents_load() -> Vec<Recent> {
50 let path = recents_path();
51 if !path.exists() {
52 return Vec::new();
53 }
54
55 match fs::read_to_string(&path) {
56 Ok(content) => serde_json::from_str(&content).unwrap_or_default(),
57 Err(_) => Vec::new(),
58 }
59 }
60
61 /// Save recent workspaces to disk
62 pub fn recents_save(recents: &[Recent]) -> Result<()> {
63 let path = recents_path();
64
65 // Ensure config directory exists
66 if let Some(parent) = path.parent() {
67 fs::create_dir_all(parent)?;
68 }
69
70 let content = serde_json::to_string_pretty(recents)?;
71 fs::write(&path, content)?;
72 Ok(())
73 }
74
75 /// Add or update a workspace in recents
76 pub fn recents_add_or_update(path: &Path) -> Result<()> {
77 let mut recents = recents_load();
78 let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
79
80 let timestamp = SystemTime::now()
81 .duration_since(UNIX_EPOCH)
82 .map(|d| d.as_secs())
83 .unwrap_or(0);
84
85 // Find existing entry
86 if let Some(existing) = recents.iter_mut().find(|r| r.path == canonical) {
87 existing.last_opened = timestamp;
88 existing.open_count += 1;
89 } else {
90 recents.push(Recent::new(canonical));
91 }
92
93 // Sort by last_opened descending (most recent first)
94 recents.sort_by(|a, b| b.last_opened.cmp(&a.last_opened));
95
96 // Keep only the most recent 50 entries
97 recents.truncate(50);
98
99 recents_save(&recents)
100 }
101
102 /// Get recent workspaces, sorted by most recently opened
103 pub fn recents_get() -> Vec<Recent> {
104 let mut recents = recents_load();
105 // Filter out non-existent directories
106 recents.retain(|r| r.path.exists());
107 recents
108 }
109