Skip to main content

xianyun_deploy/registrycache/
paths.rs

1//! Filesystem layout for the host-side registry cache.
2//!
3//! Ported from `internal/registrycache/paths.go`.
4
5use std::path::PathBuf;
6
7use thiserror::Error;
8
9/// Errors from resolving registry-cache paths.
10#[derive(Debug, Error)]
11pub enum PathError {
12    /// The user's home directory could not be determined (e.g. `HOME`
13    /// unset on Unix).
14    #[error("getting home directory: {0}")]
15    HomeDir(#[from] std::env::VarError),
16}
17
18/// The global registry-cache directory under the user's data home,
19/// relative to `$HOME`. All registry-cache state (PID file, config,
20/// log, blob storage, refs) lives here.
21const BASE_PATH: &str = ".local/share/xianyun/registry";
22/// Existing Go-caproni (and earlier Rust-port) installs persisted
23/// registry-cache state here. Recognized as a fallback location so an
24/// existing profile's cache state isn't orphaned by this rename -- see
25/// `xianyun-executor::datadir`'s matching pattern and PORT_NOTES.md.
26const LEGACY_BASE_PATH: &str = ".local/share/caproni/registry";
27
28fn home_dir() -> Result<PathBuf, PathError> {
29    // Mirrors Go's `os.UserHomeDir()`, which on Unix reads `$HOME`.
30    std::env::var("HOME")
31        .map(PathBuf::from)
32        .map_err(PathError::from)
33}
34
35/// Returns the global registry-cache directory: `~/.local/share/
36/// xianyun/registry/`, unless that doesn't exist yet AND `~/.local/
37/// share/caproni/registry/` (the pre-rename path) does, in which case
38/// the existing caproni-path directory is used instead of starting a
39/// fresh, empty one and orphaning the existing cache state.
40pub fn global_dir() -> Result<PathBuf, PathError> {
41    let home = home_dir()?;
42    let primary = home.join(BASE_PATH);
43    if primary.is_dir() {
44        return Ok(primary);
45    }
46
47    let legacy = home.join(LEGACY_BASE_PATH);
48    if legacy.is_dir() {
49        return Ok(legacy);
50    }
51
52    Ok(primary)
53}
54
55/// Returns the directory containing per-profile ref marker files.
56pub fn refs_dir() -> Result<PathBuf, PathError> {
57    Ok(global_dir()?.join("refs"))
58}
59
60/// Returns the absolute path of the ref marker file for the given
61/// profile.
62pub fn ref_path(profile: &str) -> Result<PathBuf, PathError> {
63    Ok(refs_dir()?.join(profile))
64}
65
66/// Returns the absolute path of the PID file for the zot process.
67pub fn pid_path() -> Result<PathBuf, PathError> {
68    Ok(global_dir()?.join("zot.pid"))
69}
70
71/// Returns the absolute path of the generated zot config JSON.
72pub fn config_path() -> Result<PathBuf, PathError> {
73    Ok(global_dir()?.join("config.json"))
74}
75
76/// Returns the directory where zot stores cached blobs and manifests.
77pub fn data_dir() -> Result<PathBuf, PathError> {
78    Ok(global_dir()?.join("data"))
79}
80
81/// Returns the absolute path of the zot stdout/stderr log file.
82pub fn log_path() -> Result<PathBuf, PathError> {
83    Ok(global_dir()?.join("zot.log"))
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use crate::registrycache::test_support::with_home;
90
91    // Ported from internal/registrycache/paths_test.go:12 (TestGlobalDir)
92    #[test]
93    fn test_global_dir() {
94        with_home(|home| {
95            let got = global_dir().unwrap();
96            assert_eq!(got, home.join(".local/share/xianyun/registry"));
97            assert!(got.to_str().unwrap().ends_with("/xianyun/registry"));
98        });
99    }
100
101    // Existing Go-caproni installs persisted registry-cache state under
102    // .local/share/caproni/registry/; this port must keep reading it
103    // rather than silently starting a fresh, empty .local/share/
104    // xianyun/registry/ and orphaning that state.
105    #[test]
106    fn test_global_dir_falls_back_to_existing_caproni_path() {
107        with_home(|home| {
108            std::fs::create_dir_all(home.join(".local/share/caproni/registry")).unwrap();
109
110            let got = global_dir().unwrap();
111            assert_eq!(got, home.join(".local/share/caproni/registry"));
112        });
113    }
114
115    #[test]
116    fn test_global_dir_prefers_xianyun_path_when_both_exist() {
117        with_home(|home| {
118            std::fs::create_dir_all(home.join(".local/share/xianyun/registry")).unwrap();
119            std::fs::create_dir_all(home.join(".local/share/caproni/registry")).unwrap();
120
121            let got = global_dir().unwrap();
122            assert_eq!(got, home.join(".local/share/xianyun/registry"));
123        });
124    }
125
126    // Ported from internal/registrycache/paths_test.go:23 (TestRefsDir)
127    #[test]
128    fn test_refs_dir() {
129        with_home(|home| {
130            let got = refs_dir().unwrap();
131            assert_eq!(got, home.join(".local/share/xianyun/registry/refs"));
132        });
133    }
134
135    // Ported from internal/registrycache/paths_test.go:33 (TestRefPath)
136    #[test]
137    fn test_ref_path() {
138        with_home(|home| {
139            let got = ref_path("my-profile").unwrap();
140            assert_eq!(
141                got,
142                home.join(".local/share/xianyun/registry/refs/my-profile")
143            );
144        });
145    }
146
147    // Ported from internal/registrycache/paths_test.go:43 (TestPIDPath)
148    #[test]
149    fn test_pid_path() {
150        with_home(|home| {
151            let got = pid_path().unwrap();
152            assert_eq!(got, home.join(".local/share/xianyun/registry/zot.pid"));
153        });
154    }
155
156    // Ported from internal/registrycache/paths_test.go:53 (TestConfigPath)
157    #[test]
158    fn test_config_path() {
159        with_home(|home| {
160            let got = config_path().unwrap();
161            assert_eq!(got, home.join(".local/share/xianyun/registry/config.json"));
162        });
163    }
164
165    // Ported from internal/registrycache/paths_test.go:63 (TestDataDir)
166    #[test]
167    fn test_data_dir() {
168        with_home(|home| {
169            let got = data_dir().unwrap();
170            assert_eq!(got, home.join(".local/share/xianyun/registry/data"));
171        });
172    }
173
174    // Ported from internal/registrycache/paths_test.go:73 (TestLogPath)
175    #[test]
176    fn test_log_path() {
177        with_home(|home| {
178            let got = log_path().unwrap();
179            assert_eq!(got, home.join(".local/share/xianyun/registry/zot.log"));
180        });
181    }
182}