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