opencode_cloud_core/docker/
state.rs1use chrono::Utc;
7use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9
10use super::profile::{DockerResourceNames, active_resource_names};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ImageState {
15 pub version: String,
17 pub source: String,
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub registry: Option<String>,
22 pub acquired_at: String,
24}
25
26impl ImageState {
27 pub fn prebuilt(version: &str, registry: &str) -> Self {
29 Self {
30 version: version.to_string(),
31 source: "prebuilt".to_string(),
32 registry: Some(registry.to_string()),
33 acquired_at: Utc::now().to_rfc3339(),
34 }
35 }
36
37 pub fn built(version: &str) -> Self {
39 Self {
40 version: version.to_string(),
41 source: "build".to_string(),
42 registry: None,
43 acquired_at: Utc::now().to_rfc3339(),
44 }
45 }
46}
47
48pub fn get_state_path() -> Option<PathBuf> {
50 let names = active_resource_names();
51 get_state_path_for_names(&names)
52}
53
54pub fn get_state_path_for_names(names: &DockerResourceNames) -> Option<PathBuf> {
55 crate::config::paths::get_data_dir().map(|p| p.join(&names.image_state_file))
56}
57
58pub fn save_state(state: &ImageState) -> anyhow::Result<()> {
60 let path = get_state_path().ok_or_else(|| anyhow::anyhow!("Could not determine state path"))?;
61
62 if let Some(parent) = path.parent() {
64 std::fs::create_dir_all(parent)?;
65 }
66
67 let json = serde_json::to_string_pretty(state)?;
68 std::fs::write(&path, json)?;
69 Ok(())
70}
71
72pub fn load_state() -> Option<ImageState> {
74 let path = get_state_path()?;
75 let content = std::fs::read_to_string(&path).ok()?;
76 serde_json::from_str(&content).ok()
77}
78
79pub fn clear_state() -> anyhow::Result<()> {
81 if let Some(path) = get_state_path()
82 && path.exists()
83 {
84 std::fs::remove_file(&path)?;
85 }
86 Ok(())
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn test_image_state_prebuilt() {
95 let state = ImageState::prebuilt("1.0.12", "ghcr.io");
96 assert_eq!(state.version, "1.0.12");
97 assert_eq!(state.source, "prebuilt");
98 assert_eq!(state.registry, Some("ghcr.io".to_string()));
99 assert!(!state.acquired_at.is_empty());
100 }
101
102 #[test]
103 fn test_image_state_built() {
104 let state = ImageState::built("1.0.12");
105 assert_eq!(state.version, "1.0.12");
106 assert_eq!(state.source, "build");
107 assert!(state.registry.is_none());
108 }
109
110 #[test]
111 fn test_image_state_serialize_deserialize() {
112 let state = ImageState::prebuilt("1.0.12", "docker.io");
113 let json = serde_json::to_string(&state).unwrap();
114 let parsed: ImageState = serde_json::from_str(&json).unwrap();
115 assert_eq!(state.version, parsed.version);
116 assert_eq!(state.source, parsed.source);
117 assert_eq!(state.registry, parsed.registry);
118 }
119
120 #[test]
121 fn test_get_state_path() {
122 let path = get_state_path();
123 assert!(path.is_some());
124 let p = path.unwrap();
125 assert!(p.to_string_lossy().contains("image-state.json"));
126 }
127}