reth_era/common/
file_ops.rs1use crate::e2s::{error::E2sError, types::Version};
4use std::{
5 fs::File,
6 io::{Read, Seek, Write},
7 path::Path,
8};
9
10pub trait EraFileFormat: Sized {
12 type EraGroup;
14
15 type Id: EraFileId;
17
18 fn version(&self) -> &Version;
20
21 fn group(&self) -> &Self::EraGroup;
23
24 fn id(&self) -> &Self::Id;
26
27 fn new(group: Self::EraGroup, id: Self::Id) -> Self;
29}
30
31pub trait EraFileId: Clone {
33 const FILE_TYPE: EraFileType;
35
36 const ITEMS_PER_ERA: u64;
38
39 fn network_name(&self) -> &str;
41
42 fn start_number(&self) -> u64;
44
45 fn count(&self) -> u32;
47
48 fn hash(&self) -> Option<[u8; 4]>;
50
51 fn include_era_count(&self) -> bool;
53
54 fn era_number(&self) -> u64 {
56 self.start_number() / Self::ITEMS_PER_ERA
57 }
58
59 fn era_count(&self) -> u64 {
65 if self.count() == 0 {
66 return 0;
67 }
68 let first_era = self.era_number();
69 let last_number = self.start_number() + self.count() as u64 - 1;
70 let last_era = last_number / Self::ITEMS_PER_ERA;
71 last_era - first_era + 1
72 }
73
74 fn to_file_name(&self) -> String {
76 Self::FILE_TYPE.format_filename(
77 self.network_name(),
78 self.era_number(),
79 self.hash(),
80 self.include_era_count(),
81 self.era_count(),
82 )
83 }
84}
85
86pub trait StreamReader<R: Read + Seek>: Sized {
88 type File: EraFileFormat;
90
91 type Iterator;
93
94 fn new(reader: R) -> Self;
96
97 fn read(self, network_name: String) -> Result<Self::File, E2sError>;
99
100 fn iter(self) -> Self::Iterator;
102}
103
104pub trait FileReader: StreamReader<File> {
106 fn open<P: AsRef<Path>>(
108 path: P,
109 network_name: impl Into<String>,
110 ) -> Result<Self::File, E2sError> {
111 let file = File::open(path).map_err(E2sError::Io)?;
112 let reader = Self::new(file);
113 reader.read(network_name.into())
114 }
115}
116
117pub trait StreamWriter<W: Write>: Sized {
119 type File: EraFileFormat;
121
122 fn new(writer: W) -> Self;
124
125 fn write_version(&mut self) -> Result<(), E2sError>;
127
128 fn write_file(&mut self, file: &Self::File) -> Result<(), E2sError>;
130
131 fn flush(&mut self) -> Result<(), E2sError>;
133}
134
135pub trait FileWriter {
137 type File: EraFileFormat<Id: EraFileId>;
139
140 fn create<P: AsRef<Path>>(path: P, file: &Self::File) -> Result<(), E2sError>;
142
143 fn create_with_id<P: AsRef<Path>>(directory: P, file: &Self::File) -> Result<(), E2sError>;
145}
146
147impl<T: StreamWriter<File>> FileWriter for T {
148 type File = T::File;
149
150 fn create<P: AsRef<Path>>(path: P, file: &Self::File) -> Result<(), E2sError> {
152 let file_handle = File::create(path).map_err(E2sError::Io)?;
153 let mut writer = Self::new(file_handle);
154 writer.write_file(file)?;
155 Ok(())
156 }
157
158 fn create_with_id<P: AsRef<Path>>(directory: P, file: &Self::File) -> Result<(), E2sError> {
160 let filename = file.id().to_file_name();
161 let path = directory.as_ref().join(filename);
162 Self::create(path, file)
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
168pub enum EraFileType {
169 Era,
172 Era1,
175}
176
177impl EraFileType {
178 pub const fn extension(&self) -> &'static str {
180 match self {
181 Self::Era => ".era",
182 Self::Era1 => ".era1",
183 }
184 }
185
186 pub fn from_filename(filename: &str) -> Option<Self> {
188 if filename.ends_with(".era") {
189 Some(Self::Era)
190 } else if filename.ends_with(".era1") {
191 Some(Self::Era1)
192 } else {
193 None
194 }
195 }
196
197 pub fn format_filename(
205 &self,
206 network_name: &str,
207 era_number: u64,
208 hash: Option<[u8; 4]>,
209 include_era_count: bool,
210 era_count: u64,
211 ) -> String {
212 let hash = format_hash(hash);
213
214 if include_era_count {
215 format!(
216 "{}-{:05}-{:05}-{}{}",
217 network_name,
218 era_number,
219 era_count,
220 hash,
221 self.extension()
222 )
223 } else {
224 format!("{}-{:05}-{}{}", network_name, era_number, hash, self.extension())
225 }
226 }
227
228 pub fn from_url(url: &str) -> Self {
231 if url.contains("era1") {
232 Self::Era1
233 } else {
234 Self::Era
235 }
236 }
237}
238
239pub fn format_hash(hash: Option<[u8; 4]>) -> String {
241 match hash {
242 Some(h) => format!("{:02x}{:02x}{:02x}{:02x}", h[0], h[1], h[2], h[3]),
243 None => "00000000".to_string(),
244 }
245}