1use super::{Manager, ManagerError, Receipt, Result};
6use std::path::PathBuf;
7use std::time::SystemTime;
8
9pub(super) fn store_root() -> Option<PathBuf> {
11 rootle_provider::paths::data_dir().map(|d| d.join("rootle").join("providers"))
12}
13
14pub(super) fn state_root() -> Option<PathBuf> {
16 rootle_provider::paths::state_dir().map(|d| d.join("rootle").join("providers"))
17}
18
19impl Manager {
20 pub(super) fn receipt_path(&self, name: &str) -> PathBuf {
21 self.state.join(format!("{name}.toml"))
22 }
23
24 pub(super) fn version_dir(&self, name: &str, tag: &str) -> PathBuf {
25 self.store.join(name).join(tag)
26 }
27
28 pub fn current_link(&self, name: &str) -> PathBuf {
29 self.store.join(name).join("current")
30 }
31
32 pub fn current_binary(&self, name: &str) -> Option<PathBuf> {
34 let link = self.current_link(name);
35 let resolved = std::fs::read_link(&link).ok()?;
36 let dir = link.parent()?.join(resolved);
37 let stem = format!("rootle-{name}");
38 let bin = dir.join(&stem);
39 bin.is_file().then_some(bin)
40 }
41
42 pub fn receipt(&self, name: &str) -> Option<Receipt> {
43 let text = std::fs::read_to_string(self.receipt_path(name)).ok()?;
44 toml::from_str(&text).ok()
45 }
46
47 pub fn receipts(&self) -> Vec<Receipt> {
48 let mut out = Vec::new();
49 if let Ok(entries) = std::fs::read_dir(&self.state) {
50 for entry in entries.flatten() {
51 let path = entry.path();
52 if path.extension().is_some_and(|e| e == "toml")
53 && let Ok(text) = std::fs::read_to_string(&path)
54 && let Ok(r) = toml::from_str::<Receipt>(&text)
55 {
56 out.push(r);
57 }
58 }
59 }
60 out.sort_by(|a, b| a.name.cmp(&b.name));
61 out
62 }
63
64 pub(super) fn write_receipt(&self, receipt: &Receipt) -> Result<()> {
65 std::fs::create_dir_all(&self.state)?;
66 let path = self.receipt_path(&receipt.name);
67 let tmp = path.with_extension("toml.tmp");
68 let text =
69 toml::to_string_pretty(receipt).map_err(|e| ManagerError::User(e.to_string()))?;
70 std::fs::write(&tmp, text)?;
71 std::fs::rename(&tmp, &path).map_err(ManagerError::Io)?;
72 Ok(())
73 }
74
75 pub fn pin(&self, name: &str, tag: Option<String>) -> Result<()> {
78 let mut receipt = self
79 .receipt(name)
80 .ok_or_else(|| ManagerError::User(format!("{name} is not installed")))?;
81 if let Some(tag) = tag {
82 receipt.tag = tag;
83 }
84 receipt.pinned = true;
85 self.write_receipt(&receipt)
86 }
87
88 pub fn unpin(&self, name: &str) -> Result<()> {
89 let mut receipt = self
90 .receipt(name)
91 .ok_or_else(|| ManagerError::User(format!("{name} is not installed")))?;
92 receipt.pinned = false;
93 self.write_receipt(&receipt)
94 }
95
96 pub fn remove(&self, name: &str) -> Result<()> {
97 let path = self.receipt_path(name);
98 if !path.exists() {
99 return Err(ManagerError::User(format!("{name} is not installed")));
100 }
101 let dir = self.store.join(name);
102 let _ = std::fs::remove_dir_all(dir);
103 std::fs::remove_file(path)?;
104 println!("{name} removed");
105 Ok(())
106 }
107
108 pub(super) fn point_current(&self, name: &str, tag: &str) -> Result<()> {
109 let link = self.current_link(name);
110 let _ = std::fs::remove_file(&link);
111 #[cfg(unix)]
112 std::os::unix::fs::symlink(format!("{tag}/"), &link).map_err(ManagerError::Io)?;
113 Ok(())
114 }
115}
116
117pub(super) fn now_iso() -> Option<String> {
118 let secs = SystemTime::now()
119 .duration_since(SystemTime::UNIX_EPOCH)
120 .ok()?
121 .as_secs();
122 let days = (secs / 86_400) as i64;
124 let (y, m, d) = civil_from_days(days);
125 let (h, min, s) = ((secs % 86_400) / 3600, (secs % 3600) / 60, secs % 60);
126 Some(format!("{y:04}-{m:02}-{d:02}T{h:02}:{min:02}:{s:02}Z"))
127}
128
129fn civil_from_days(z: i64) -> (i64, u32, u32) {
130 let z = z + 719_468;
131 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
132 let doe = (z - era * 146_097) as u64;
133 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
134 let y = yoe as i64 + era * 400;
135 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
136 let mp = (5 * doy + 2) / 153;
137 let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
138 let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
139 (if m <= 2 { y + 1 } else { y }, m, d)
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn iso_date_math() {
148 assert_eq!(civil_from_days(0), (1970, 1, 1));
149 assert_eq!(civil_from_days(19_723), (2024, 1, 1));
150 assert_eq!(civil_from_days(20690), (2026, 8, 25));
151 }
152}