jujube_lib/
simple_op_store.rs1use std::fmt::Debug;
16use std::fs;
17use std::fs::File;
18use std::io::ErrorKind;
19use std::io::Write;
20use std::path::PathBuf;
21
22use blake2::{Blake2b, Digest};
23use protobuf::{Message, ProtobufError};
24use tempfile::{NamedTempFile, PersistError};
25
26use crate::op_store::{
27 OpStore, OpStoreError, OpStoreResult, Operation, OperationId, OperationMetadata, View, ViewId,
28};
29use crate::store::{CommitId, MillisSinceEpoch, Timestamp};
30
31impl From<std::io::Error> for OpStoreError {
32 fn from(err: std::io::Error) -> Self {
33 OpStoreError::Other(err.to_string())
34 }
35}
36
37impl From<PersistError> for OpStoreError {
38 fn from(err: PersistError) -> Self {
39 OpStoreError::Other(err.to_string())
40 }
41}
42
43impl From<ProtobufError> for OpStoreError {
44 fn from(err: ProtobufError) -> Self {
45 OpStoreError::Other(err.to_string())
46 }
47}
48
49#[derive(Debug)]
50pub struct SimpleOpStore {
51 path: PathBuf,
52}
53
54impl SimpleOpStore {
55 pub fn init(store_path: PathBuf) -> Self {
56 fs::create_dir(store_path.join("views")).unwrap();
57 fs::create_dir(store_path.join("operations")).unwrap();
58 Self::load(store_path)
59 }
60
61 pub fn load(store_path: PathBuf) -> Self {
62 SimpleOpStore { path: store_path }
63 }
64
65 fn view_path(&self, id: &ViewId) -> PathBuf {
66 self.path.join("views").join(id.hex())
67 }
68
69 fn operation_path(&self, id: &OperationId) -> PathBuf {
70 self.path.join("operations").join(id.hex())
71 }
72}
73
74fn not_found_to_store_error(err: std::io::Error) -> OpStoreError {
75 if err.kind() == ErrorKind::NotFound {
76 OpStoreError::NotFound
77 } else {
78 OpStoreError::from(err)
79 }
80}
81
82impl OpStore for SimpleOpStore {
83 fn read_view(&self, id: &ViewId) -> OpStoreResult<View> {
84 let path = self.view_path(&id);
85 let mut file = File::open(path).map_err(not_found_to_store_error)?;
86
87 let proto: crate::protos::op_store::View = protobuf::parse_from_reader(&mut file)?;
88 Ok(view_from_proto(&proto))
89 }
90
91 fn write_view(&self, view: &View) -> OpStoreResult<ViewId> {
92 let temp_file = NamedTempFile::new_in(&self.path)?;
93
94 let proto = view_to_proto(view);
95 let mut proto_bytes: Vec<u8> = Vec::new();
96 proto.write_to_writer(&mut proto_bytes)?;
97
98 temp_file.as_file().write_all(&proto_bytes)?;
99
100 let id = ViewId(Blake2b::digest(&proto_bytes).to_vec());
101
102 temp_file.persist(self.view_path(&id))?;
103 Ok(id)
104 }
105
106 fn read_operation(&self, id: &OperationId) -> OpStoreResult<Operation> {
107 let path = self.operation_path(&id);
108 let mut file = File::open(path).map_err(not_found_to_store_error)?;
109
110 let proto: crate::protos::op_store::Operation = protobuf::parse_from_reader(&mut file)?;
111 Ok(operation_from_proto(&proto))
112 }
113
114 fn write_operation(&self, operation: &Operation) -> OpStoreResult<OperationId> {
115 let temp_file = NamedTempFile::new_in(&self.path)?;
116
117 let proto = operation_to_proto(operation);
118 let mut proto_bytes: Vec<u8> = Vec::new();
119 proto.write_to_writer(&mut proto_bytes)?;
120
121 temp_file.as_file().write_all(&proto_bytes)?;
122
123 let id = OperationId(Blake2b::digest(&proto_bytes).to_vec());
124
125 temp_file.persist(self.operation_path(&id))?;
126 Ok(id)
127 }
128}
129
130fn timestamp_to_proto(timestamp: &Timestamp) -> crate::protos::op_store::Timestamp {
131 let mut proto = crate::protos::op_store::Timestamp::new();
132 proto.set_millis_since_epoch(timestamp.timestamp.0);
133 proto.set_tz_offset(timestamp.tz_offset);
134 proto
135}
136
137fn timestamp_from_proto(proto: &crate::protos::op_store::Timestamp) -> Timestamp {
138 Timestamp {
139 timestamp: MillisSinceEpoch(proto.millis_since_epoch),
140 tz_offset: proto.tz_offset,
141 }
142}
143
144fn operation_metadata_to_proto(
145 metadata: &OperationMetadata,
146) -> crate::protos::op_store::OperationMetadata {
147 let mut proto = crate::protos::op_store::OperationMetadata::new();
148 proto.set_start_time(timestamp_to_proto(&metadata.start_time));
149 proto.set_end_time(timestamp_to_proto(&metadata.end_time));
150 proto.set_description(metadata.description.clone());
151 proto.set_hostname(metadata.hostname.clone());
152 proto.set_username(metadata.username.clone());
153 proto
154}
155
156fn operation_metadata_from_proto(
157 proto: &crate::protos::op_store::OperationMetadata,
158) -> OperationMetadata {
159 let start_time = timestamp_from_proto(proto.get_start_time());
160 let end_time = timestamp_from_proto(proto.get_end_time());
161 let description = proto.get_description().to_owned();
162 let hostname = proto.get_hostname().to_owned();
163 let username = proto.get_username().to_owned();
164 OperationMetadata {
165 start_time,
166 end_time,
167 description,
168 hostname,
169 username,
170 }
171}
172
173fn operation_to_proto(operation: &Operation) -> crate::protos::op_store::Operation {
174 let mut proto = crate::protos::op_store::Operation::new();
175 proto.set_view_id(operation.view_id.0.clone());
176 for parent in &operation.parents {
177 proto.parents.push(parent.0.clone());
178 }
179 proto.set_metadata(operation_metadata_to_proto(&operation.metadata));
180 proto
181}
182
183fn operation_from_proto(proto: &crate::protos::op_store::Operation) -> Operation {
184 let operation_id_from_proto = |parent: &Vec<u8>| OperationId(parent.clone());
185 let parents = proto.parents.iter().map(operation_id_from_proto).collect();
186 let view_id = ViewId(proto.view_id.to_vec());
187 let metadata = operation_metadata_from_proto(proto.get_metadata());
188 Operation {
189 view_id,
190 parents,
191 metadata,
192 }
193}
194
195fn view_to_proto(view: &View) -> crate::protos::op_store::View {
196 let mut proto = crate::protos::op_store::View::new();
197 proto.checkout = view.checkout.0.clone();
198 for head_id in &view.head_ids {
199 proto.head_ids.push(head_id.0.clone());
200 }
201 proto
202}
203
204fn view_from_proto(proto: &crate::protos::op_store::View) -> View {
205 let mut view = View::new(CommitId(proto.checkout.clone()));
206 for head_id_bytes in proto.head_ids.iter() {
207 view.head_ids.insert(CommitId(head_id_bytes.to_vec()));
208 }
209 view
210}