1use std::path::{Path, PathBuf};
2
3#[derive(Debug, Clone)]
5pub struct PackageCache {
6 root: PathBuf,
7}
8
9impl PackageCache {
10 pub fn default_location() -> Result<Self, String> {
12 let home = std::env::var("HOME")
13 .or_else(|_| std::env::var("USERPROFILE"))
14 .map_err(|_| "Could not determine home directory".to_string())?;
15 Ok(PackageCache {
16 root: PathBuf::from(home).join(".tl").join("packages"),
17 })
18 }
19
20 pub fn new(root: PathBuf) -> Self {
22 PackageCache { root }
23 }
24
25 pub fn root(&self) -> &Path {
27 &self.root
28 }
29
30 pub fn package_dir(&self, name: &str, version: &str) -> PathBuf {
32 self.root.join(name).join(version)
33 }
34
35 pub fn is_cached(&self, name: &str, version: &str) -> bool {
37 self.package_dir(name, version).exists()
38 }
39
40 pub fn list_versions(&self, name: &str) -> Vec<String> {
42 let pkg_dir = self.root.join(name);
43 if !pkg_dir.is_dir() {
44 return Vec::new();
45 }
46 let mut versions = Vec::new();
47 if let Ok(entries) = std::fs::read_dir(&pkg_dir) {
48 for entry in entries.flatten() {
49 if entry.path().is_dir()
50 && let Some(name) = entry.file_name().to_str()
51 {
52 versions.push(name.to_string());
53 }
54 }
55 }
56 versions.sort();
57 versions
58 }
59
60 pub fn remove(&self, name: &str, version: &str) -> Result<(), String> {
62 let dir = self.package_dir(name, version);
63 if dir.exists() {
64 std::fs::remove_dir_all(&dir)
65 .map_err(|e| format!("Failed to remove cached package: {e}"))?;
66 }
67 let pkg_dir = self.root.join(name);
69 if pkg_dir.exists()
70 && let Ok(entries) = std::fs::read_dir(&pkg_dir)
71 && entries.count() == 0
72 {
73 let _ = std::fs::remove_dir(&pkg_dir);
74 }
75 Ok(())
76 }
77
78 pub fn source_root(&self, name: &str, version: &str) -> Option<PathBuf> {
81 let dir = self.package_dir(name, version);
82 if !dir.exists() {
83 return None;
84 }
85 let src_dir = dir.join("src");
87 if src_dir.is_dir() {
88 return Some(src_dir);
89 }
90 Some(dir)
92 }
93
94 pub fn ensure_dir(&self) -> Result<(), String> {
96 std::fs::create_dir_all(&self.root)
97 .map_err(|e| format!("Failed to create cache directory: {e}"))
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use tempfile::TempDir;
105
106 #[test]
107 fn cache_dir_layout() {
108 let dir = TempDir::new().unwrap();
109 let cache = PackageCache::new(dir.path().to_path_buf());
110
111 let pkg_dir = cache.package_dir("mylib", "1.0.0");
112 assert!(pkg_dir.ends_with("mylib/1.0.0"));
113 }
114
115 #[test]
116 fn cache_is_cached() {
117 let dir = TempDir::new().unwrap();
118 let cache = PackageCache::new(dir.path().to_path_buf());
119
120 assert!(!cache.is_cached("mylib", "1.0.0"));
121
122 let pkg_dir = cache.package_dir("mylib", "1.0.0");
123 std::fs::create_dir_all(&pkg_dir).unwrap();
124 assert!(cache.is_cached("mylib", "1.0.0"));
125 }
126
127 #[test]
128 fn cache_list_versions() {
129 let dir = TempDir::new().unwrap();
130 let cache = PackageCache::new(dir.path().to_path_buf());
131
132 assert!(cache.list_versions("mylib").is_empty());
133
134 std::fs::create_dir_all(cache.package_dir("mylib", "1.0.0")).unwrap();
135 std::fs::create_dir_all(cache.package_dir("mylib", "2.0.0")).unwrap();
136
137 let versions = cache.list_versions("mylib");
138 assert_eq!(versions, vec!["1.0.0", "2.0.0"]);
139 }
140
141 #[test]
142 fn cache_remove() {
143 let dir = TempDir::new().unwrap();
144 let cache = PackageCache::new(dir.path().to_path_buf());
145
146 std::fs::create_dir_all(cache.package_dir("mylib", "1.0.0")).unwrap();
147 assert!(cache.is_cached("mylib", "1.0.0"));
148
149 cache.remove("mylib", "1.0.0").unwrap();
150 assert!(!cache.is_cached("mylib", "1.0.0"));
151 }
152
153 #[test]
154 fn cache_source_root() {
155 let dir = TempDir::new().unwrap();
156 let cache = PackageCache::new(dir.path().to_path_buf());
157
158 assert!(cache.source_root("mylib", "1.0.0").is_none());
160
161 let pkg_dir = cache.package_dir("mylib", "1.0.0");
163 std::fs::create_dir_all(pkg_dir.join("src")).unwrap();
164 let root = cache.source_root("mylib", "1.0.0").unwrap();
165 assert!(root.ends_with("src"));
166
167 let cache2 = PackageCache::new(dir.path().to_path_buf());
169 let pkg_dir2 = cache2.package_dir("nolib", "1.0.0");
170 std::fs::create_dir_all(&pkg_dir2).unwrap();
171 let root2 = cache2.source_root("nolib", "1.0.0").unwrap();
172 assert!(root2.ends_with("1.0.0"));
173 }
174}