jj_lib/
simple_op_heads_store.rs1#![allow(missing_docs)]
16
17use std::any::Any;
18use std::fmt::Debug;
19use std::fmt::Formatter;
20use std::fs;
21use std::io;
22use std::path::Path;
23use std::path::PathBuf;
24
25use thiserror::Error;
26
27use crate::backend::BackendInitError;
28use crate::file_util::IoResultExt as _;
29use crate::file_util::PathError;
30use crate::lock::FileLock;
31use crate::object_id::ObjectId as _;
32use crate::op_heads_store::OpHeadsStore;
33use crate::op_heads_store::OpHeadsStoreError;
34use crate::op_heads_store::OpHeadsStoreLock;
35use crate::op_store::OperationId;
36
37#[derive(Debug, Error)]
39#[error("Failed to initialize simple operation heads store")]
40pub struct SimpleOpHeadsStoreInitError(#[from] pub PathError);
41
42impl From<SimpleOpHeadsStoreInitError> for BackendInitError {
43 fn from(err: SimpleOpHeadsStoreInitError) -> Self {
44 BackendInitError(err.into())
45 }
46}
47
48pub struct SimpleOpHeadsStore {
49 dir: PathBuf,
50}
51
52impl Debug for SimpleOpHeadsStore {
53 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
54 f.debug_struct("SimpleOpHeadsStore")
55 .field("dir", &self.dir)
56 .finish()
57 }
58}
59
60impl SimpleOpHeadsStore {
61 pub fn name() -> &'static str {
62 "simple_op_heads_store"
63 }
64
65 pub fn init(dir: &Path) -> Result<Self, SimpleOpHeadsStoreInitError> {
66 let op_heads_dir = dir.join("heads");
67 fs::create_dir(&op_heads_dir).context(&op_heads_dir)?;
68 Ok(Self { dir: op_heads_dir })
69 }
70
71 pub fn load(dir: &Path) -> Self {
72 let op_heads_dir = dir.join("heads");
73 Self { dir: op_heads_dir }
74 }
75
76 fn add_op_head(&self, id: &OperationId) -> io::Result<()> {
77 std::fs::write(self.dir.join(id.hex()), "")
78 }
79
80 fn remove_op_head(&self, id: &OperationId) -> io::Result<()> {
81 std::fs::remove_file(self.dir.join(id.hex())).or_else(|err| {
82 if err.kind() == io::ErrorKind::NotFound {
83 Ok(())
88 } else {
89 Err(err)
90 }
91 })
92 }
93}
94
95struct SimpleOpHeadsStoreLock {
96 _lock: FileLock,
97}
98
99impl OpHeadsStoreLock for SimpleOpHeadsStoreLock {}
100
101impl OpHeadsStore for SimpleOpHeadsStore {
102 fn as_any(&self) -> &dyn Any {
103 self
104 }
105
106 fn name(&self) -> &str {
107 Self::name()
108 }
109
110 fn update_op_heads(
111 &self,
112 old_ids: &[OperationId],
113 new_id: &OperationId,
114 ) -> Result<(), OpHeadsStoreError> {
115 assert!(!old_ids.contains(new_id));
116 self.add_op_head(new_id)
117 .map_err(|err| OpHeadsStoreError::Write {
118 new_op_id: new_id.clone(),
119 source: err.into(),
120 })?;
121 for old_id in old_ids {
122 self.remove_op_head(old_id)
123 .map_err(|err| OpHeadsStoreError::Write {
124 new_op_id: new_id.clone(),
125 source: err.into(),
126 })?;
127 }
128 Ok(())
129 }
130
131 fn get_op_heads(&self) -> Result<Vec<OperationId>, OpHeadsStoreError> {
132 let mut op_heads = vec![];
133 for op_head_entry in
134 std::fs::read_dir(&self.dir).map_err(|err| OpHeadsStoreError::Read(err.into()))?
135 {
136 let op_head_file_name = op_head_entry
137 .map_err(|err| OpHeadsStoreError::Read(err.into()))?
138 .file_name();
139 let op_head_file_name = op_head_file_name.to_str().ok_or_else(|| {
140 OpHeadsStoreError::Read(
141 format!("Non-utf8 in op head file name: {op_head_file_name:?}").into(),
142 )
143 })?;
144 if let Ok(op_head) = hex::decode(op_head_file_name) {
145 op_heads.push(OperationId::new(op_head));
146 }
147 }
148 Ok(op_heads)
149 }
150
151 fn lock(&self) -> Result<Box<dyn OpHeadsStoreLock + '_>, OpHeadsStoreError> {
152 let lock = FileLock::lock(self.dir.join("lock"))
153 .map_err(|err| OpHeadsStoreError::Lock(err.into()))?;
154 Ok(Box::new(SimpleOpHeadsStoreLock { _lock: lock }))
155 }
156}