1use std::cell::{RefCell, RefMut};
2use std::fs::File;
3use std::io::{ErrorKind, Seek, SeekFrom};
4use std::marker::PhantomData;
5use std::os::unix::fs::FileExt;
6use std::path::Path;
7
8use super::{id_iter, EntityHead, EntityItem, ENTITY_META};
9use crate::models::{Binary, Gene, GeneId};
10use crate::{utils, DbError, NotFound, ShahError, SystemError};
11
12pub trait EntityKochFrom<Old: EntityItem, State = ()>: Sized {
15 fn entity_koch_from(
16 old: Old, state: RefMut<State>,
17 ) -> Result<Self, ShahError>;
18}
19
20impl<T: EntityItem, S> EntityKochFrom<T, S> for T {
21 fn entity_koch_from(old: T, _: RefMut<S>) -> Result<Self, ShahError> {
22 Ok(old)
23 }
24}
25
26#[crate::model]
31#[derive(Debug)]
32pub struct EntityKochProg {
33 pub total: GeneId,
34 pub prog: GeneId,
35}
36
37id_iter!(EntityKochProg);
38
39#[derive(Debug)]
44pub struct EntityKoch<New, Old: EntityItem, State>
45where
46 New: EntityItem + EntityKochFrom<Old, State>,
47{
48 pub from: EntityKochDb<Old>,
49 pub state: RefCell<State>,
50 pub total: GeneId,
51 _new: PhantomData<New>,
53}
54
55impl<New, Old, State> EntityKoch<New, Old, State>
56where
57 New: EntityItem + EntityKochFrom<Old, State>,
58 Old: EntityItem,
59{
60 pub fn new(from: EntityKochDb<Old>, state: State) -> Self {
61 Self {
62 total: from.total,
64 from,
65 state: RefCell::new(state),
66 _new: PhantomData::<New>,
67 }
68 }
69
70 pub fn get_id(&self, gene_id: GeneId) -> Result<New, ShahError> {
71 if gene_id == 0 {
72 return Ok(New::default());
73 }
74
75 let mut old = Old::default();
76 self.from.get_id(gene_id, &mut old)?;
77 New::entity_koch_from(old, self.state.borrow_mut())
78 }
79
80 pub fn get(&self, gene: &Gene) -> Result<New, ShahError> {
81 if gene.id == 0 {
82 return Ok(New::default());
83 }
84
85 let mut old = Old::default();
86 self.from.get(gene, &mut old)?;
87 New::entity_koch_from(old, self.state.borrow_mut())
88 }
89}
90
91#[derive(Debug)]
96pub struct EntityKochDb<T: EntityItem> {
97 file: File,
98 revision: u16,
99 total: GeneId,
100 ls: String,
101 _e: PhantomData<T>,
102}
103
104impl<T: EntityItem> EntityKochDb<T> {
105 pub fn new(path: &str, revision: u16) -> Result<Self, ShahError> {
106 let path = Path::new("data/").join(path);
107 let name = path
108 .file_name()
109 .and_then(|v| v.to_str())
110 .expect("could not get file_name from path");
111
112 utils::validate_db_name(name)?;
113
114 let open_path = path.join(format!("{name}.{revision}.shah"));
115 log::debug!("opening: {open_path:?} for koching");
116 let file = std::fs::OpenOptions::new()
117 .read(true)
118 .write(false)
119 .create(false)
120 .truncate(false)
121 .open(open_path)?;
122
123 let mut db = Self {
124 file,
125 revision,
126 total: GeneId(0),
127 ls: format!("<EntityKochDb {name}.{revision}>"),
128 _e: PhantomData::<T>,
129 };
130
131 db.init()?;
132
133 Ok(db)
134 }
135
136 pub fn file_size(&mut self) -> std::io::Result<u64> {
137 self.file.seek(SeekFrom::End(0))
138 }
139
140 fn init(&mut self) -> Result<(), ShahError> {
141 let file_size = self.file_size()?;
142 if file_size < ENTITY_META + T::N {
143 log::error!("{} db content is not valid", self.ls);
144 return Err(DbError::InvalidDbContent)?;
145 }
146
147 self.check_head()?;
148
149 self.total = GeneId((file_size - ENTITY_META) / T::N);
150
151 Ok(())
152 }
153
154 fn check_head(&self) -> Result<(), ShahError> {
155 let mut head = EntityHead::default();
156 self.file.read_exact_at(head.as_binary_mut(), 0)?;
157
158 head.check::<T>(self.revision, &self.ls)?;
159
160 Ok(())
161 }
162
163 fn id_to_pos(id: GeneId) -> u64 {
170 ENTITY_META + (id * T::N).0
171 }
172
173 pub fn read_buf_at<B: Binary>(
174 &self, buf: &mut B, id: GeneId,
175 ) -> Result<(), ShahError> {
176 let pos = Self::id_to_pos(id);
177 match self.file.read_exact_at(buf.as_binary_mut(), pos) {
178 Ok(_) => Ok(()),
179 Err(e) => match e.kind() {
180 ErrorKind::UnexpectedEof => Err(NotFound::OutOfBounds)?,
181 _ => {
182 log::error!("{} read_buf_at: {e:?}", self.ls);
183 Err(e)?
184 }
185 },
186 }
187 }
188
189 pub fn read_at(&self, entity: &mut T, id: GeneId) -> Result<(), ShahError> {
190 self.read_buf_at(entity, id)
191 }
192
193 pub fn get_id(
194 &self, gene_id: GeneId, entity: &mut T,
195 ) -> Result<(), ShahError> {
196 self.read_at(entity, gene_id)?;
197
198 if entity.gene().is_none() {
199 return Err(NotFound::EmptyItem)?;
200 }
201
202 if gene_id != entity.gene().id {
203 log::error!("{} get_id: gene id mismatch", self.ls);
204 return Err(SystemError::GeneIdMismatch)?;
205 }
206
207 Ok(())
208 }
209
210 pub fn get(&self, gene: &Gene, entity: &mut T) -> Result<(), ShahError> {
211 gene.validate()?;
212 self.read_at(entity, gene.id)?;
213 gene.check(entity.gene(), &self.ls)?;
214 Ok(())
215 }
216}
217
218