shah/db/entity/
koch.rs

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
12// =========== EntityKochFrom trait ===========
13
14pub 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// =========== end of EntityKochFrom trait ===========
27
28// =========== EntityKochProg struct ===========
29
30#[crate::model]
31#[derive(Debug)]
32pub struct EntityKochProg {
33    pub total: GeneId,
34    pub prog: GeneId,
35}
36
37id_iter!(EntityKochProg);
38
39// =========== end of EntityKochProg struct ===========
40
41// =========== EntityKoch struct ===========
42
43#[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    // pub prog: u64,
52    _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            // prog: 0,
63            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// =========== end of EntityKoch struct ===========
92
93// =========== EntityKochDb struct ===========
94
95#[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    // pub fn seek_id(&mut self, id: GeneId) -> Result<(), ShahError> {
164    //     let pos = ENTITY_META + id * T::N;
165    //     self.file.seek(SeekFrom::Start(pos))?;
166    //     Ok(())
167    // }
168
169    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// =========== end of EntityKochDb struct ===========