1use std::fs;
31use std::io::{Error, ErrorKind, Read};
32use std::path::{Path, PathBuf};
33
34use log::warn;
35
36#[derive(Debug)]
37pub struct FileSeq {
38 path_1: PathBuf,
39 path_2: PathBuf,
40}
41
42impl FileSeq {
43 pub fn new<P: AsRef<Path>>(store_dir: P, initial_value: u64) -> std::io::Result<Self> {
44 let store_path = store_dir.as_ref();
45
46 fs::create_dir_all(store_path)?;
47 let store_path_buf = store_path.to_path_buf();
48 let path_1 = store_path_buf.join("_1.seq");
49 let path_2 = store_path_buf.join("_2.seq");
50
51 let seq = Self { path_1, path_2 };
52
53 seq.initialize_if_necessary(initial_value)?;
54
55 Ok(seq)
56 }
57
58 fn initialize_if_necessary(&self, initial_value: u64) -> std::io::Result<()> {
59 if fs::metadata(&self.path_1).is_ok() || fs::metadata(&self.path_2).is_ok() {
60 Ok(())
61 } else {
62 self.write(initial_value)
63 }
64 }
65
66 pub fn delete(&self) -> () {
90 let _ = fs::remove_file(&self.path_1);
92 let _ = fs::remove_file(&self.path_2);
93 }
94
95 pub fn get_and_increment(&self, increment: u64) -> std::io::Result<u64> {
113 let value = self.read()?;
114 self.write(value + increment)?;
115 Ok(value)
116 }
117
118 pub fn increment_and_get(&self, increment: u64) -> std::io::Result<u64> {
136 let value = self.get_and_increment(increment)?;
137 Ok(value + increment)
138 }
139
140 pub fn value(&self) -> std::io::Result<u64> {
157 self.read()
158 }
159
160 fn read(&self) -> std::io::Result<u64> {
161 let mut value1: Option<u64> = None;
162 if fs::metadata(&self.path_1).is_ok() {
163 let value = self.read_from_path(&self.path_1)?;
164 value1 = Some(value);
165 }
166
167 let mut value2: Option<u64> = None;
168 if fs::metadata(&self.path_2).is_ok() {
169 value2 = self.read_from_path(&self.path_2).ok();
170 }
171
172 match value2 {
173 Some(v2) => match value1 {
174 Some(v1) => {
175 if v2 > v1 {
176 Ok(v2)
177 } else {
178 warn!("Latest sequence value is smaller than backup, using backup.");
179 fs::remove_file(&self.path_2).ok();
180 Ok(v1)
181 }
182 }
183 None => Ok(v2),
184 },
185 None => {
186 fs::remove_file(&self.path_2).ok();
187
188 match value1 {
189 Some(v1) => Ok(v1),
190 None => Err(Error::new(
191 ErrorKind::InvalidData,
192 "Looks like both backup and latest sequence files are corrupted.",
193 )),
194 }
195 }
196 }
197 }
198
199 fn read_from_path<P: AsRef<Path>>(&self, path: P) -> std::io::Result<u64> {
200 let mut buff = [0; 8];
201 let mut f = fs::File::open(path.as_ref())?;
202 f.read_exact(&mut buff)?;
203 let value = u64::from_be_bytes(buff);
204 Ok(value)
205 }
206
207 fn write(&self, value: u64) -> std::io::Result<()> {
208 if fs::metadata(&self.path_2).is_ok() {
209 fs::rename(&self.path_2, &self.path_1)?;
210 }
211 self.write_to_path(&self.path_2, value)
212 }
213
214 fn write_to_path<P: AsRef<Path>>(&self, path: P, value: u64) -> std::io::Result<()> {
215 fs::write(path.as_ref(), value.to_be_bytes())
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use std::env;
222 use std::fs;
223 use std::path::PathBuf;
224
225 use rand::RngCore;
226
227 use crate::FileSeq;
228
229 pub fn tmpdir() -> PathBuf {
230 let p = env::temp_dir();
231 let mut r = rand::thread_rng();
232 let ret = p.join(&format!("file-seq-{}", r.next_u32()));
233 fs::create_dir(&ret).unwrap();
234 ret
235 }
236
237 #[test]
238 fn should_store_initial_seq_correctly() {
239 let dir = tmpdir();
240 let seq = FileSeq::new(&dir, 1).unwrap();
241 assert!(std::fs::metadata(dir).is_ok());
242 assert!(std::fs::metadata(seq.path_2).is_ok());
243 }
244
245 #[test]
246 fn should_cycle_seq_files() {
247 let dir = tmpdir();
248 let seq = FileSeq::new(&dir, 1).unwrap();
249 assert!(std::fs::metadata(dir).is_ok());
250 assert!(std::fs::metadata(&seq.path_2).is_ok());
251 let path_2_value = std::fs::read(&seq.path_2).unwrap();
252 seq.increment_and_get(1).unwrap();
253 let path_1_value = std::fs::read(&seq.path_1).unwrap();
254 assert_eq!(path_2_value, path_1_value);
255 }
256
257 #[test]
258 fn should_delete() {
259 let dir = tmpdir();
260 let seq = FileSeq::new(&dir, 1).unwrap();
261 assert!(std::fs::metadata(dir).is_ok());
262 assert!(std::fs::metadata(&seq.path_2).is_ok());
263 seq.increment_and_get(1).unwrap();
264 seq.delete();
265 assert!(!std::fs::metadata(&seq.path_1).is_ok());
266 assert!(!std::fs::metadata(&seq.path_2).is_ok());
267 }
268
269 #[test]
270 fn should_increment_and_get() {
271 let dir = tmpdir();
272 let seq = FileSeq::new(dir, 1).unwrap();
273 let prev_value = seq.value().unwrap();
274 let curr_value = seq.increment_and_get(1).unwrap();
275 assert_eq!(prev_value + 1, curr_value);
276 assert_eq!(curr_value, seq.value().unwrap());
277 }
278
279 #[test]
280 fn should_get_and_increment() {
281 let dir = tmpdir();
282 let seq = FileSeq::new(dir, 1).unwrap();
283 let prev_value = seq.value().unwrap();
284 let curr_value = seq.get_and_increment(1).unwrap();
285 assert_eq!(prev_value, curr_value);
286 assert_eq!(curr_value + 1, seq.value().unwrap())
287 }
288}