1pub mod collection;
2
3pub use collection::*;
4use soulog::*;
5use lazy_db::*;
6use crate::{entry::*, search::Searchable};
7use toml::Table;
8
9macro_rules! get {
11 ($key:ident at $moc:ident from $table:ident as $func:ident with $logger:ident) => {{
12 let key = stringify!($key);
13 let obj = unwrap_opt!(($table.get(key)) with $logger, format: MOC("moc '{0}' must have '{key}' attribute", $moc));
14
15 unwrap_opt!((obj.$func()) with $logger, format: MOC("moc '{0}'s '{key}' attribute must be of correct type", $moc))
16 }};
17
18 ($var:ident = $key:ident at $entry:ident from $table:ident as $func:ident with $logger:ident or $default:expr) => {
19 let key = stringify!($key);
20 let default = $default;
21 let $var = $table.get(key)
22 .map(|x| unwrap_opt!((x.$func()) with $logger, format: MOC("moc '{0}'s '{key}' attribute must be of the correct type", $entry)))
23 .unwrap_or(&default);
24 };
25}
26
27pub struct MOC {
28 pub container: LazyContainer,
29 pub uid: String,
30 pub title: Option<String>,
31 pub description: Option<String>,
32 pub notes: Option<Box<[String]>>,
33 pub tags: Option<Box<[String]>>,
34 pub collections: Option<Box<[Collection]>>,
35}
36
37impl MOC {
38 pub fn store_lazy(&self, mut logger: impl Logger) {
39 log!((logger) MOC("Storing moc into archive..."));
40 if let Some(x) = &self.title { write_db_container!(MOC(self.container) title = new_string(x) with logger); }
42 if let Some(x) = &self.description { write_db_container!(MOC(self.container) description = new_string(x) with logger); }
43
44 if let Some(x) = &self.notes {
46 list::write(
47 x.as_ref(),
48 |file, data| LazyData::new_string(file, data),
49 &if_err!((logger) [MOC, err => ("While writing notes to archive: {:?}", err)] retry self.container.new_container("notes")),
50 logger.hollow()
51 );
52 }
53
54 if let Some(x) = &self.tags {
55 list::write(
56 x.as_ref(),
57 |file, data| LazyData::new_string(file, data),
58 &if_err!((logger) [MOC, err => ("While writing tags to archive: {:?}", err)] retry self.container.new_container("tags")),
59 logger.hollow()
60 );
61 }
62 }
63
64 pub fn load_lazy(uid: String, container: LazyContainer) -> Self {
65 Self {
66 container,
67 uid,
68 title: None,
69 description: None,
70 notes: None,
71 tags: None,
72 collections: None,
73 }
74 }
75
76 pub fn clear_cache(&mut self) {
77 self.title = None;
78 self.description = None;
79 self.notes = None;
80 self.tags = None;
81 self.collections = None;
82 }
83
84 cache_field!(title(this, logger) -> String {
85 read_db_container!(title from MOCSection(this.container) as collect_string with logger)
86 });
87
88 cache_field!(description(this, logger) -> String {
89 read_db_container!(description from MOC(this.container) as collect_string with logger)
90 });
91
92 cache_field!(notes(this, logger) -> Box<[String]> {
93 list::read(
94 |data| data.collect_string(),
95 &if_err!((logger) [MOC, err => ("While reading from moc's notes: {err:?}")] retry this.container.child_container("notes")),
96 logger
97 )
98 });
99
100 cache_field!(tags(this, logger) -> Box<[String]> {
101 list::read(
102 |data| data.collect_string(),
103 &if_err!((logger) [MOC, err => ("While reading from moc's tags: {err:?}")] retry this.container.child_container("tags")),
104 logger
105 )
106 });
107
108 cache_field!(collections(this, logger) -> Box<[Collection]> {
109 let container = if_err!((logger) [MOC, err => ("While reading from moc's collections: {err:?}")] retry this.container.child_container("collections"));
110 let length = if_err!((logger) [MOC, err => ("While reading from moc's collections' length: {err:?}")] retry container.read_data("length"));
111 let length = if_err!((logger) [MOC, err => ("While reading from moc's collections' length: {err:?}")] {length.collect_u16()} crash {
112 log!((logger) MOC("{err:#?}") as Fatal);
113 logger.crash()
114 });
115 let mut colletions = Vec::with_capacity(length as usize);
116
117 for i in 0..length {
118 colletions.push(Collection::load_lazy(
119 if_err!((logger) [MOC, err => ("While reading moc collection {i}: {err:?}")] retry container.child_container(i.to_string()))
120 ));
121 }
122
123 colletions.into_boxed_slice()
124 });
125
126 pub fn new(table: Table, moc_path: &str, database: LazyContainer, mut logger: impl Logger) -> Self {
127 log!((logger) MOC("Reading moc '{moc_path}'s raw unchecked data..."));
128
129 let moc_table = get!(moc at moc_path from table as as_table with logger);
130 let uid = get!(uid at moc_path from moc_table as as_str with logger).to_string();
131
132 let title = get!(title at moc_path from moc_table as as_str with logger).to_string();
133 let description = get!(description at moc_path from moc_table as as_str with logger).to_string();
134 get!(raw_notes = notes at moc_path from moc_table as as_array with logger or Vec::<toml::Value>::with_capacity(0));
135 let raw_tags = get!(tags at moc_path from moc_table as as_array with logger);
136 let raw_collections = get!(collection at moc_path from table as as_array with logger);
137
138 let container =
140 if_err!((logger) [MOC, err => ("While initialising moc: '{err:?}'")] retry database.new_container(&uid));
141
142 log!((logger) MOC("Parsing notes & tags"));
144 unpack_array!(notes from raw_notes with logger by x
145 => unwrap_opt!((x.as_str()) with logger, format: MOC("All notes in moc '{moc_path}' must be strings")).to_string()
146 );
147
148 unpack_array!(tags from raw_tags with logger by x
149 => unwrap_opt!((x.as_str()) with logger, format: MOC("All tags in moc '{moc_path}' must be strings")).to_string()
150 );
151
152 log!((logger) MOC("Parsing moc's collections..."));
154 let list = if_err!((logger) [MOC, err => ("While initialising collections: {err:?}")] retry container.new_container("collections"));
155 unpack_array!(collections from raw_collections with logger by (i, x) => {
156 let container = if_err!((logger) [MOC, err => ("While initialising collection {i}: {err:?}")] retry list.new_container(i.to_string()));
157 let table = unwrap_opt!((x.as_table()) with logger, format: MOC("MOC '{moc_path}', collection {i} must be a toml table"));
158 Collection::new(table, container, moc_path, i as u8, logger.hollow()) });
160 if_err!((logger) [MOC, err => ("While writing collection list length: {err:?}")] retry write_container!((list) length = new_u16(raw_collections.len() as u16)));
161
162 log!((logger) Entry("Storing moc's parsed and checked data into archive..."));
163
164 let mut this = Self {
165 uid,
166 container,
167 title: Some(title),
168 description: Some(description),
169 notes: Some(notes.into_boxed_slice()),
170 tags: Some(tags.into_boxed_slice()),
171 collections: Some(collections.into_boxed_slice()),
172 };
173 this.store_lazy(logger.hollow());
174 log!((logger) MOC("Successfully written moc into archive"));
175 log!((logger) MOC(""));
176 this.clear_cache();
177 this
178 }
179
180 pub fn pull(&mut self, logger: impl Logger) -> Table {
181 let mut map = Table::new();
182 let mut moc = Table::new();
183
184 moc.insert("uid".into(), self.uid.clone().into());
186 moc.insert("title".into(), self.title(logger.hollow()).clone().into());
187 moc.insert("description".into(), self.description(logger.hollow()).clone().into());
188 moc.insert("notes".into(), self.notes(logger.hollow()).to_vec().into());
189 moc.insert("tags".into(), self.tags(logger.hollow()).to_vec().into());
190 map.insert("moc".into(), moc.into());
191 map.insert("is-moc".into(), true.into());
192
193 self.clear_cache();
194
195 map.insert("collection".into(), self.collections(logger.hollow())
196 .iter_mut()
197 .map(|x| x.pull(logger.hollow()))
198 .collect::<Vec<Table>>()
199 .into()
200 );
201
202 self.clear_cache();
203
204 map
205 }
206}
207
208impl Searchable for MOC {
209 fn get_uid(&self) -> String {
210 self.uid.clone()
211 }
212
213 fn contains_tag(&mut self, tag: &String, logger: impl Logger) -> bool {
214 let result = self.tags(logger).contains(tag);
215 self.tags = None;
216 result
217 }
218}