Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_o3db_sync/src/file/zdir.rs

8.3 KiB, 50 runs

created by r1870400018:803, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use crate::{
2 prelude::*,
3 file::{
4 core::{
5 FileAccess,
6 FileType,
7 },
8 floc::FileNum,
9 live::{
10 LiveFile,
11 LivePair,
12 },
13 },
14 format_data_file,
15};
16
17use oxedyne_fe2o3_core::prelude::*;
18use oxedyne_fe2o3_jdat::{
19 prelude::*,
20};
21use oxedyne_fe2o3_text::string::Stringer;
22
23use std::{
24 fs::{
25 File,
26 OpenOptions,
27 },
28 path::{
29 Path,
30 PathBuf,
31 },
32};
33
34/// Allows zone information held in `Config` to be read from a `Daticle` map.
35#[derive(Clone, Debug, FromDatMap)]
36pub struct ZoneDirStr {
37 pub dir: String,
38 pub max_size: u64,
39}
40
41impl Default for ZoneDirStr {
42 fn default() -> Self {
43 Self {
44 dir: fmt!(""),
45 max_size: constant::DEFAULT_MAX_ZONE_DIR_BYTES,
46 }
47 }
48}
49
50#[derive(Clone, Debug, Default)]
51pub struct ZoneDir {
52 pub dir: PathBuf,
53 pub max_size: u64,
54}
55
56impl ZoneDir {
57
58 pub fn file_seq_name(n: FileNum) -> String {
59 Stringer::new(fmt!(format_data_file!(), n)).insert_every("_", 3).into_inner()
60 }
61
62 pub fn relative_file_path(typ: &FileType, n: FileNum) -> PathBuf {
63 let mut result = PathBuf::new();
64 match typ {
65 FileType::Data => {
66 result.push(Self::file_seq_name(n));
67 result.set_extension(constant::DATA_FILE_EXT);
68 },
69 FileType::Index => {
70 result.push(Self::file_seq_name(n));
71 result.set_extension(constant::INDEX_FILE_EXT);
72 },
73 }
74 result
75 }
76
77 pub fn relative_gc_temp_path(typ: &FileType, n: FileNum) -> PathBuf {
78 let mut result = PathBuf::from(constant::GC_TEMP_FILE_PREFIX);
79 result.set_extension(Self::relative_file_path(typ, n));
80 result
81 }
82
83 pub fn is_gc_temp_file(p: &Path) -> bool {
84 match p.file_name().and_then(|s| s.to_str()) {
85 Some(name) => name.starts_with(constant::GC_TEMP_FILE_PREFIX),
86 None => false,
87 }
88 }
89
90 pub fn open_ozone_file(
91 &self,
92 fnum: FileNum,
93 typ: &FileType,
94 how: &FileAccess,
95 )
96 -> Outcome<(PathBuf, File)>
97 {
98 let mut path = self.dir.clone();
99 path.push(Self::relative_file_path(typ, fnum));
100 let file = res!(Self::open_file(&path, how));
101 Ok((path, file))
102 }
103
104 pub fn claim(&self, fnum: FileNum)
105 -> Outcome<bool>
106 {
107 let mut won = Vec::new();
108 for typ in [FileType::Data, FileType::Index] {
109 let mut path = self.dir.clone();
110 path.push(Self::relative_file_path(&typ, fnum));
111 match OpenOptions::new().write(true).create_new(true).open(&path) {
112 Ok(_) => won.push(path),
113 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => return Ok(false),
114 Err(e) => return Err(err!(e,
115 "While claiming {:?} for zone file {}.", path, fnum;
116 IO, File, Create)),
117 }
118 }
119 Ok(!won.is_empty())
120 }
121
122 pub fn open_file(p: &PathBuf, access: &FileAccess) -> Outcome<File> {
123 match access {
124 FileAccess::Reading => match OpenOptions::new()
125 .read(true)
126 .open(p)
127 {
128 Err(e) => Err(err!(e,
129 "While opening file {:?} for {:?}", p, access;
130 IO, File, Read)),
131 Ok(file) => Ok(file),
132 },
133 FileAccess::Writing => match OpenOptions::new()
134 .create(true)
135 .read(true)
136 .write(true)
137 .append(true)
138 .open(p)
139 {
140 Err(e) => Err(err!(e,
141 "While opening file {:?} for {:?}", p, access;
142 IO, File, Write, Create)),
143 Ok(file) => Ok(file),
144 },
145 }
146 }
147
148 pub fn ozone_file_number_and_type(path: &Path) -> Outcome<(FileNum, FileType)> {
149 let stem_str = match path.file_stem() {
150 None => return Err(err!(
151 "File {:?} has an invalid file name. Ozone zone directories \
152 should not contain files with names like this.", path;
153 IO, File, Path, Invalid)),
154 Some(os_str) => match os_str.to_str() {
155 None => return Err(err!(
156 "File {:?} file name is not valid Unicode", path;
157 IO, File, Path, Decode, Invalid)),
158 Some(s) => s.replace("_", ""),
159 },
160 };
161 let fnum = res!(stem_str.parse::<FileNum>());
162 if fnum == 0 {
163 return Err(err!(
164 "File {:?} has an invalid file number. Ozone data and index \
165 file numbers start from 1.", path;
166 IO, File, Path, Invalid));
167 }
168 let ftyp = match path.extension() {
169 None => return Err(err!(
170 "File {:?} has an invalid file extension. Ozone zone directories \
171 should not contain files of this type.", path;
172 IO, File, Path, Invalid)),
173 Some(os_str) => match os_str.to_str() {
174 None => return Err(err!(
175 "File {:?} file extension is not valid Unicode", path;
176 IO, File, Path, Decode, Invalid)),
177 Some(s) => match s {
178 constant::DATA_FILE_EXT => FileType::Data,
179 constant::INDEX_FILE_EXT => FileType::Index,
180 _ => return Err(err!(
181 "File {:?} extension not valid for Ozone database", path;
182 IO, File, Name, Invalid)),
183 },
184 },
185 };
186 Ok((fnum, ftyp))
187 }
188
189 pub fn open_live(&self, fnum: FileNum) -> Outcome<LivePair> {
190 let (path, file) = res!(self.open_ozone_file(
191 fnum,
192 &FileType::Data,
193 &FileAccess::Writing,
194 ));
195 let mut dat = LiveFile {
196 path,
197 file: Some(file),
198 size: 0,
199 };
200 dat.size = res!(dat.get_file_len());
201 let (path, file) = res!(self.open_ozone_file(
202 fnum,
203 &FileType::Index,
204 &FileAccess::Writing,
205 ));
206 let mut ind = LiveFile {
207 path,
208 file: Some(file),
209 size: 0,
210 };
211 ind.size = res!(ind.get_file_len());
212 Ok(LivePair {
213 fnum,
214 dat,
215 ind,
216 })
217 }
218}
219
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 fn scratch(what: &str)
226 -> Outcome<ZoneDir>
227 {
228 let dir = std::env::temp_dir().join(fmt!(
229 "o3db_claim_{}_{}", std::process::id(), what));
230 let _ = std::fs::remove_dir_all(&dir);
231 res!(std::fs::create_dir_all(&dir));
232 Ok(ZoneDir { dir, max_size: 0 })
233 }
234
235 #[test]
236 fn a_file_number_is_claimed_by_one_writer_only() -> Outcome<()> {
237 let zdir = res!(scratch("one"));
238 assert!(res!(zdir.claim(7)), "the first claim on a free number was refused");
239 assert!(!res!(zdir.claim(7)), "a number already claimed was handed out twice");
240 // And a different number is unaffected by the first one being taken.
241 assert!(res!(zdir.claim(8)), "an untouched number could not be claimed");
242 let _ = std::fs::remove_dir_all(&zdir.dir);
243 Ok(())
244 }
245
246 #[test]
247 fn a_claim_takes_the_data_file_and_the_index_together() -> Outcome<()> {
248 let zdir = res!(scratch("pair"));
249 assert!(res!(zdir.claim(3)), "the claim was refused");
250 for typ in [FileType::Data, FileType::Index] {
251 let mut path = zdir.dir.clone();
252 path.push(ZoneDir::relative_file_path(&typ, 3));
253 assert!(path.is_file(), "the claim left no {:?} file at {:?}", typ, path);
254 }
255 let _ = std::fs::remove_dir_all(&zdir.dir);
256 Ok(())
257 }
258
259 #[test]
260 fn half_a_pair_is_enough_to_refuse_a_number() -> Outcome<()> {
261 let zdir = res!(scratch("half"));
262 let mut path = zdir.dir.clone();
263 path.push(ZoneDir::relative_file_path(&FileType::Data, 5));
264 res!(std::fs::write(&path, b""));
265 assert!(!res!(zdir.claim(5)),
266 "a number whose data file was already there was claimed anyway");
267 let _ = std::fs::remove_dir_all(&zdir.dir);
268 Ok(())
269 }
270}