1use super::{
2 EventStore, ExpectedVersion, InMemoryStore,
3 frame::{self, ScanOutcome},
4};
5use crate::{Codec, MemoryError, NewEvent, Result, StoredEvent, StreamId};
6use std::{
7 fs::{File, OpenOptions},
8 io::{Seek, SeekFrom, Write},
9 path::{Path, PathBuf},
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum RecoveryPolicy {
14 Strict,
15 TruncatePartialTail,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Durability {
20 Flush,
21 SyncData,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct FileStoreOptions {
26 pub recovery: RecoveryPolicy,
27 pub durability: Durability,
28 pub max_frame_bytes: usize,
29}
30
31impl Default for FileStoreOptions {
32 fn default() -> Self {
33 Self {
34 recovery: RecoveryPolicy::Strict,
35 durability: Durability::SyncData,
36 max_frame_bytes: 256 * 1024 * 1024,
37 }
38 }
39}
40
41pub struct FileEventStore<E, C> {
42 path: PathBuf,
43 file: File,
44 codec: C,
45 options: FileStoreOptions,
46 inner: InMemoryStore<E>,
47 durable_len: u64,
48}
49
50impl<E, C> FileEventStore<E, C>
51where
52 E: Clone,
53 C: Codec<StoredEvent<E>>,
54{
55 pub fn open(path: impl AsRef<Path>, codec: C, options: FileStoreOptions) -> Result<Self> {
62 if options.max_frame_bytes < 4 {
63 return Err(MemoryError::InvalidValue {
64 field: "max_frame_bytes",
65 reason: "must fit at least the event-count field",
66 });
67 }
68 let path = path.as_ref().to_path_buf();
69 let mut file = OpenOptions::new()
70 .read(true)
71 .write(true)
72 .create(true)
73 .truncate(false)
74 .open(&path)
75 .map_err(|error| io("open event log", error))?;
76 if file
77 .metadata()
78 .map_err(|error| io("read event log metadata", error))?
79 .len()
80 == 0
81 {
82 file.write_all(frame::FILE_HEADER)
83 .map_err(|error| io("write event log header", error))?;
84 sync(&file, options.durability)?;
85 }
86 let outcome = frame::scan(&mut file, &codec, options.max_frame_bytes)?;
87 let (events, durable_len, partial) = match outcome {
88 ScanOutcome::Complete {
89 events,
90 durable_len,
91 } => (events, durable_len, false),
92 ScanOutcome::PartialTail {
93 events,
94 durable_len,
95 } => (events, durable_len, true),
96 };
97 if partial && options.recovery == RecoveryPolicy::Strict {
98 return Err(MemoryError::CorruptLog {
99 offset: durable_len,
100 reason: "partial trailing batch".to_owned(),
101 });
102 }
103 if partial {
104 file.set_len(durable_len)
105 .map_err(|error| io("truncate partial event batch", error))?;
106 sync(&file, options.durability)?;
107 }
108 file.seek(SeekFrom::End(0))
109 .map_err(|error| io("seek event log end", error))?;
110 Ok(Self {
111 path,
112 file,
113 codec,
114 options,
115 inner: InMemoryStore::restore(events)?,
116 durable_len,
117 })
118 }
119
120 #[must_use]
121 pub fn path(&self) -> &Path {
122 &self.path
123 }
124
125 fn persist(&mut self, events: &[StoredEvent<E>]) -> Result<()> {
126 if events.is_empty() {
127 return Ok(());
128 }
129 let actual_len = self
130 .file
131 .metadata()
132 .map_err(|error| io("read event log metadata", error))?
133 .len();
134 if actual_len != self.durable_len {
135 return Err(MemoryError::ExternalModification);
136 }
137 let frame = frame::encode_batch(events, &self.codec, self.options.max_frame_bytes)?;
138 self.file
139 .seek(SeekFrom::Start(self.durable_len))
140 .map_err(|error| io("seek append position", error))?;
141 if let Err(error) = self.file.write_all(&frame) {
142 self.rollback_tail()?;
143 return Err(io("append event batch", error));
144 }
145 if let Err(error) = sync(&self.file, self.options.durability) {
146 self.rollback_tail()?;
147 return Err(error);
148 }
149 self.durable_len = self
150 .durable_len
151 .checked_add(u64::try_from(frame.len()).map_err(|_| MemoryError::CapacityOverflow)?)
152 .ok_or(MemoryError::CapacityOverflow)?;
153 Ok(())
154 }
155
156 fn rollback_tail(&mut self) -> Result<()> {
157 self.file
158 .set_len(self.durable_len)
159 .map_err(|error| io("rollback partial event batch", error))?;
160 self.file
161 .seek(SeekFrom::Start(self.durable_len))
162 .map_err(|error| io("seek after rollback", error))?;
163 sync(&self.file, self.options.durability)
164 }
165}
166
167impl<E, C> EventStore<E> for FileEventStore<E, C>
168where
169 E: Clone,
170 C: Codec<StoredEvent<E>>,
171{
172 fn append(
173 &mut self,
174 stream: &StreamId,
175 expected: ExpectedVersion,
176 events: &[NewEvent<E>],
177 ) -> Result<Vec<StoredEvent<E>>> {
178 let committed = self.inner.prepare_append(stream, expected, events)?;
179 self.persist(&committed)?;
180 self.inner.commit_prepared(&committed);
181 Ok(committed)
182 }
183
184 fn append_owned(
185 &mut self,
186 stream: &StreamId,
187 expected: ExpectedVersion,
188 events: Vec<NewEvent<E>>,
189 ) -> Result<Vec<StoredEvent<E>>> {
190 let committed = self.inner.prepare_append_owned(stream, expected, events)?;
191 self.persist(&committed)?;
192 self.inner.commit_prepared(&committed);
193 Ok(committed)
194 }
195
196 fn load_stream(&self, stream: &StreamId, after: Option<u64>) -> Vec<StoredEvent<E>> {
197 self.inner.load_stream(stream, after)
198 }
199
200 fn load_all(&self, after: Option<u64>, limit: usize) -> Vec<StoredEvent<E>> {
201 self.inner.load_all(after, limit)
202 }
203
204 fn stream_version(&self, stream: &StreamId) -> Option<u64> {
205 self.inner.stream_version(stream)
206 }
207
208 fn len(&self) -> usize {
209 self.inner.len()
210 }
211}
212
213fn sync(file: &File, durability: Durability) -> Result<()> {
214 match durability {
215 Durability::Flush => Ok(()),
216 Durability::SyncData => file
217 .sync_data()
218 .map_err(|error| io("sync event log", error)),
219 }
220}
221
222#[allow(clippy::needless_pass_by_value)]
223fn io(operation: &'static str, error: std::io::Error) -> MemoryError {
224 MemoryError::Io {
225 operation,
226 message: error.to_string(),
227 }
228}