vtcode_memory/
manifest.rs1use std::fs;
2use std::path::Path;
3
4use serde::{Deserialize, Serialize};
5
6use crate::SessionManifest;
7use crate::TurnIndex;
8use crate::error::SessionStoreError;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16pub(crate) struct PendingCapRewrite {
17 pub(crate) previous_file_len: u64,
18 pub(crate) new_file_len: u64,
19 pub(crate) retained_turn_base: u64,
20}
21
22pub struct ManifestStore {
28 session_dir: std::path::PathBuf,
29}
30
31impl ManifestStore {
32 pub(crate) fn new(session_dir: std::path::PathBuf) -> Self {
34 Self { session_dir }
35 }
36
37 fn manifest_path(&self) -> std::path::PathBuf {
39 self.session_dir.join("manifest.json")
40 }
41
42 fn turns_path(&self) -> std::path::PathBuf {
44 self.session_dir.join("index").join("turns.json")
45 }
46
47 fn pending_cap_rewrite_path(&self) -> std::path::PathBuf {
49 self.session_dir.join("index").join("pending-cap-rewrite.json")
50 }
51
52 pub(crate) fn load_manifest(&self) -> Result<Option<SessionManifest>, SessionStoreError> {
58 let path = self.manifest_path();
59 let Some(bytes) = read_optional_private_file(&path)? else {
60 return Ok(None);
61 };
62 Ok(serde_json::from_slice(&bytes).ok())
63 }
64
65 pub(crate) fn load_turn_index(&self) -> Result<Option<TurnIndex>, SessionStoreError> {
69 let path = self.turns_path();
70 let Some(bytes) = read_optional_private_file(&path)? else {
71 return Ok(None);
72 };
73 Ok(serde_json::from_slice(&bytes).ok())
74 }
75
76 pub(crate) fn load_pending_cap_rewrite(&self) -> Result<Option<PendingCapRewrite>, SessionStoreError> {
78 let path = self.pending_cap_rewrite_path();
79 let Some(bytes) = read_optional_private_file(&path)? else {
80 return Ok(None);
81 };
82 Ok(serde_json::from_slice(&bytes).ok())
83 }
84
85 pub(crate) fn write_pending_cap_rewrite(&self, pending: &PendingCapRewrite) -> Result<(), SessionStoreError> {
87 let path = self.pending_cap_rewrite_path();
88 let bytes = serde_json::to_vec(pending)?;
89 vtcode_commons::VtCodePaths::write_private_file_atomic(&path, &bytes)
90 .map_err(|error| SessionStoreError::io(path, std::io::Error::other(error)))
91 }
92
93 pub(crate) fn clear_pending_cap_rewrite(&self) -> Result<(), SessionStoreError> {
95 let path = self.pending_cap_rewrite_path();
96 match fs::remove_file(&path) {
97 Ok(()) => Ok(()),
98 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
99 Err(error) => Err(SessionStoreError::io(path, error)),
100 }
101 }
102 pub(crate) fn write_manifest(&self, manifest: &SessionManifest) -> Result<(), SessionStoreError> {
104 let path = self.manifest_path();
105 let bytes = serde_json::to_vec(manifest)?;
106 vtcode_commons::VtCodePaths::write_private_file_atomic(&path, &bytes)
107 .map_err(|error| SessionStoreError::io(path.clone(), std::io::Error::other(error)))?;
108 crate::query::invalidate_manifest_cache(&path);
109 Ok(())
110 }
111
112 pub(crate) fn write_turn_index(&self, index: &TurnIndex) -> Result<(), SessionStoreError> {
114 let path = self.turns_path();
115 let bytes = serde_json::to_vec(index)?;
116 vtcode_commons::VtCodePaths::write_private_file_atomic(&path, &bytes)
117 .map_err(|error| SessionStoreError::io(path, std::io::Error::other(error)))
118 }
119}
120
121fn read_optional_private_file(path: &Path) -> Result<Option<Vec<u8>>, SessionStoreError> {
122 match fs::symlink_metadata(path) {
123 Ok(_) => vtcode_commons::VtCodePaths::read_file_no_follow(path)
124 .map(Some)
125 .map_err(|error| SessionStoreError::io(path.to_path_buf(), std::io::Error::other(error))),
126 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
127 Err(error) => Err(SessionStoreError::io(path.to_path_buf(), error)),
128 }
129}