1use crate::error::Result;
2use crate::script::{IntoScriptName, ScriptInfo, ScriptName};
3use crate::script_type::ScriptType;
4use crate::tag::{Tag, TagSelectorGroup};
5use chrono::{Duration, Utc};
6use fxhash::FxHashMap as HashMap;
7use hyper_scripter_historian::{Event, EventData, Historian, LastTimeRecord};
8use sqlx::SqlitePool;
9use std::collections::hash_map::Entry::{self, *};
10
11pub mod helper;
12pub use helper::RepoEntry;
13
14#[derive(Clone, Debug)]
15pub struct RecentFilter {
16 pub recent: Option<u32>,
17 pub archaeology: bool,
18}
19
20#[derive(Debug, Clone, Copy, Eq, PartialEq)]
21pub enum Visibility {
22 Normal,
23 All,
24 Inverse,
25}
26impl Visibility {
27 pub fn is_normal(&self) -> bool {
28 matches!(self, Self::Normal)
29 }
30 pub fn is_all(&self) -> bool {
31 matches!(self, Self::All)
32 }
33 pub fn is_inverse(&self) -> bool {
34 matches!(self, Self::Inverse)
35 }
36 pub fn invert(self) -> Self {
37 match self {
38 Self::Normal => Self::Inverse,
39 Self::Inverse => Self::Normal,
40 Self::All => {
41 log::warn!("無效的可見度反轉:all => all");
42 Self::All
43 }
44 }
45 }
46}
47
48#[derive(Debug)]
49enum TraceOption {
50 Normal,
51 NoTrace,
53 Humble,
55}
56
57#[derive(Debug)]
58pub struct DBEnv {
59 info_pool: SqlitePool,
60 pub historian: Historian,
61 trace_opt: TraceOption,
62 modifies_script: bool,
63}
64
65pub struct RepoEntryOptional<'b> {
66 entry: Entry<'b, String, ScriptInfo>,
67 env: &'b DBEnv,
68}
69impl<'b> RepoEntryOptional<'b> {
70 pub async fn or_insert(self, info: ScriptInfo) -> Result<RepoEntry<'b>> {
71 let exist = matches!(&self.entry, Occupied(_));
72 let info = self.entry.or_insert(info);
73 if !exist {
74 log::debug!("往資料庫塞新腳本 {:?}", info);
75 let id = self.env.handle_insert(info).await?;
76 log::debug!("往資料庫新增腳本成功,得 id = {}", id);
77 info.set_id(id as i64);
78 }
79 Ok(RepoEntry::new(info, self.env))
80 }
81}
82
83impl DBEnv {
84 pub async fn close(self) {
85 futures::join!(self.info_pool.close(), self.historian.close());
86
87 #[cfg(debug_assertions)]
89 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
90 }
91 pub fn new(info_pool: SqlitePool, historian: Historian, modifies_script: bool) -> Self {
92 Self {
93 info_pool,
94 historian,
95 modifies_script,
96 trace_opt: TraceOption::Normal,
97 }
98 }
99 pub async fn handle_neglect(&self, id: i64) -> Result {
100 let time = Utc::now().naive_utc();
101 sqlx::query!(
102 "
103 INSERT OR IGNORE INTO last_events (script_id) VALUES(?);
104 UPDATE last_events SET neglect = ? WHERE script_id = ?
105 ",
106 id,
107 time,
108 id
109 )
110 .execute(&self.info_pool)
111 .await?;
112 Ok(())
113 }
114
115 pub async fn update_last_time_directly(&self, last_time: LastTimeRecord) -> Result {
116 let LastTimeRecord {
117 script_id,
118 exec_time,
119 exec_done_time,
120 humble_time,
121 } = last_time;
122 sqlx::query!(
123 "UPDATE last_events set humble = ?, exec = ?, exec_done = ? WHERE script_id = ?",
124 humble_time,
125 exec_time,
126 exec_done_time,
127 script_id
128 )
129 .execute(&self.info_pool)
130 .await?;
131 Ok(())
132 }
133 async fn update_last_time(&self, info: &ScriptInfo) -> Result {
134 let exec_count = info.exec_count as i32;
135 match self.trace_opt {
136 TraceOption::NoTrace => return Ok(()),
137 TraceOption::Normal => (),
138 TraceOption::Humble => {
139 let humble_time = info.last_major_time();
141 sqlx::query!(
142 "UPDATE last_events set humble = ?, exec_count = ? WHERE script_id = ?",
143 humble_time,
144 exec_count,
145 info.id,
146 )
147 .execute(&self.info_pool)
148 .await?;
149 return Ok(());
150 }
151 }
152
153 let exec_time = info.exec_time.as_ref().map(|t| **t);
154 let exec_done_time = info.exec_done_time.as_ref().map(|t| **t);
155 let neglect_time = info.neglect_time.as_ref().map(|t| **t);
156 sqlx::query!(
157 "
158 INSERT OR REPLACE INTO last_events
159 (script_id, read, write, exec, exec_done, neglect, humble, exec_count)
160 VALUES(?, ?, ?, ?, ?, ?, ?, ?)
161 ",
162 info.id,
163 *info.read_time,
164 *info.write_time,
165 exec_time,
166 exec_done_time,
167 neglect_time,
168 info.humble_time,
169 exec_count
170 )
171 .execute(&self.info_pool)
172 .await?;
173 Ok(())
174 }
175
176 async fn handle_delete(&self, id: i64) -> Result {
177 assert!(self.modifies_script);
178 self.historian.remove(id).await?;
179 log::debug!("清理腳本 {:?} 的最新事件", id);
180 sqlx::query!("DELETE FROM last_events WHERE script_id = ?", id)
181 .execute(&self.info_pool)
182 .await?;
183 sqlx::query!("DELETE from script_infos where id = ?", id)
184 .execute(&self.info_pool)
185 .await?;
186 Ok(())
187 }
188
189 async fn handle_insert(&self, info: &ScriptInfo) -> Result<i64> {
190 assert!(self.modifies_script);
191 let name_cow = info.name.key();
192 let name = name_cow.as_ref();
193 let ty = info.ty.as_ref();
194 let tags = join_tags(info.tags.iter());
195 let res = sqlx::query!(
196 "
197 INSERT INTO script_infos (name, ty, tags)
198 VALUES(?, ?, ?)
199 RETURNING id
200 ",
201 name,
202 ty,
203 tags,
204 )
205 .fetch_one(&self.info_pool)
206 .await?;
207 Ok(res.id)
208 }
209
210 async fn handle_change(&self, info: &ScriptInfo) -> Result<i64> {
211 log::debug!("開始修改資料庫 {:?}", info);
212 if info.changed {
213 assert!(self.modifies_script);
214 let name = info.name.key();
215 let name = name.as_ref();
216 let tags = join_tags(info.tags.iter());
217 let ty = info.ty.as_ref();
218 sqlx::query!(
219 "UPDATE script_infos SET name = ?, tags = ?, ty = ? where id = ?",
220 name,
221 tags,
222 ty,
223 info.id,
224 )
225 .execute(&self.info_pool)
226 .await?;
227 }
228
229 if matches!(self.trace_opt, TraceOption::NoTrace) {
230 return Ok(0);
231 }
232
233 let mut last_event_id = 0;
234 macro_rules! record_event {
235 ($time:expr, $data:expr) => {
236 self.historian.record(&Event {
237 script_id: info.id,
238 humble: matches!(self.trace_opt, TraceOption::Humble),
239 time: $time,
240 data: $data,
241 })
242 };
243 }
244
245 if let Some(time) = info.exec_done_time.as_ref() {
246 if let Some(&(code, main_event_id)) = time.data() {
247 log::debug!("{:?} 的執行完畢事件", info.name);
248 last_event_id = record_event!(
249 **time,
250 EventData::ExecDone {
251 code,
252 main_event_id,
253 }
254 )
255 .await?;
256
257 if last_event_id != 0 {
258 self.update_last_time(info).await?;
259 } else {
260 log::info!("{:?} 的執行完畢事件被忽略了", info.name);
261 }
262 return Ok(last_event_id); }
264 }
265
266 self.update_last_time(info).await?;
267
268 if info.read_time.has_changed() {
269 log::debug!("{:?} 的讀取事件", info.name);
270 last_event_id = record_event!(*info.read_time, EventData::Read).await?;
271 }
272 if info.write_time.has_changed() {
273 log::debug!("{:?} 的寫入事件", info.name);
274 last_event_id = record_event!(*info.write_time, EventData::Write).await?;
275 }
276 if let Some(time) = info.exec_time.as_ref() {
277 if let Some((content, args, envs, dir)) = time.data() {
278 log::debug!("{:?} 的執行事件", info.name);
279 last_event_id = record_event!(
280 **time,
281 EventData::Exec {
282 content,
283 args,
284 envs,
285 dir: dir.as_deref(),
286 }
287 )
288 .await?;
289 }
290 }
291
292 Ok(last_event_id)
293 }
294}
295
296fn join_tags<'a, I: Iterator<Item = &'a Tag>>(tags: I) -> String {
297 let tags_arr: Vec<&str> = tags.map(|t| t.as_ref()).collect();
298 tags_arr.join(",")
299}
300
301#[derive(Debug)]
302pub struct ScriptRepo {
303 map: HashMap<String, ScriptInfo>,
304 hidden_map: HashMap<String, ScriptInfo>,
305 latest_name: Option<String>,
306 db_env: DBEnv,
307}
308
309macro_rules! iter_by_vis {
310 ($self:expr, $vis:expr) => {{
311 let (iter, iter2) = match $vis {
312 Visibility::Normal => ($self.map.iter_mut(), None),
313 Visibility::All => ($self.map.iter_mut(), Some($self.hidden_map.iter_mut())),
314 Visibility::Inverse => ($self.hidden_map.iter_mut(), None),
315 };
316 iter.chain(iter2.into_iter().flatten()).map(|(_, v)| v)
317 }};
318}
319
320impl ScriptRepo {
321 pub async fn close(self) {
322 self.db_env.close().await;
323 }
324 pub fn iter(&self) -> impl Iterator<Item = &ScriptInfo> {
325 self.map.iter().map(|(_, info)| info)
326 }
327 pub fn iter_mut(&mut self, visibility: Visibility) -> impl Iterator<Item = RepoEntry<'_>> {
328 iter_by_vis!(self, visibility).map(|info| RepoEntry::new(info, &self.db_env))
329 }
330 pub fn historian(&self) -> &Historian {
331 &self.db_env.historian
332 }
333 pub async fn new(
334 recent: Option<RecentFilter>,
335 db_env: DBEnv,
336 selector: &TagSelectorGroup,
337 ) -> Result<ScriptRepo> {
338 let mut hidden_map = HashMap::<String, ScriptInfo>::default();
339 let mut map: HashMap<String, ScriptInfo> = Default::default();
340 let time_bound = recent.map(|r| {
341 (
342 r.archaeology,
343 r.recent.map(|r| {
344 let mut time = Utc::now().naive_utc();
345 time -= Duration::days(r.into());
346 time
347 }),
348 )
349 });
350
351 let scripts = sqlx::query!(
352 "SELECT * FROM script_infos si LEFT JOIN last_events le ON si.id = le.script_id"
353 )
354 .fetch_all(&db_env.info_pool)
355 .await?;
356 for record in scripts.into_iter() {
357 let name = record.name;
358 log::trace!("載入腳本:{} {} {}", name, record.ty, record.tags);
359 let script_name = name.clone().into_script_name_unchecked()?; let mut builder = ScriptInfo::builder(
362 record.id,
363 script_name,
364 ScriptType::new_unchecked(record.ty),
365 record.tags.split(',').filter_map(|s| {
366 if s.is_empty() {
367 None
368 } else {
369 Some(Tag::new_unchecked(s.to_string()))
370 }
371 }),
372 );
373
374 builder.created_time(record.created_time);
375 builder.exec_count(record.exec_count.unwrap_or_default() as u64);
376 if let Some(time) = record.write {
377 builder.write_time(time);
378 }
379 if let Some(time) = record.read {
380 builder.read_time(time);
381 }
382 if let Some(time) = record.exec {
383 builder.exec_time(time);
384 }
385 if let Some(time) = record.exec_done {
386 builder.exec_done_time(time);
387 }
388 if let Some(time) = record.neglect {
389 builder.neglect_time(time);
390 }
391 if let Some(time) = record.humble {
392 builder.humble_time(time);
393 }
394
395 let script = builder.build();
396 let mut hide = false;
397
398 if let Some(neglect) = record.neglect {
399 log::debug!("腳本 {} 曾於 {} 被忽略", script.name, neglect);
400 }
401 if let Some((archaeology, time_bound)) = time_bound {
402 let time_bound = std::cmp::max(time_bound, record.neglect);
403 let overtime = if let Some(time_bound) = time_bound {
404 time_bound > script.last_major_time()
405 } else {
406 false
407 };
408 hide = archaeology ^ overtime;
409 }
410
411 if !hide {
412 hide = !selector.select(&script.tags, &script.ty);
413 }
414
415 if hide {
416 hidden_map.insert(name, script);
417 } else {
418 log::trace!("腳本 {:?} 通過篩選", name);
419 map.insert(name, script);
420 }
421 }
422 Ok(ScriptRepo {
423 map,
424 hidden_map,
425 latest_name: None,
426 db_env,
427 })
428 }
429 pub fn no_trace(&mut self) {
430 self.db_env.trace_opt = TraceOption::NoTrace;
431 }
432 pub fn humble(&mut self) {
433 self.db_env.trace_opt = TraceOption::Humble;
434 }
435 pub fn latest_mut(&mut self, n: usize, visibility: Visibility) -> Option<RepoEntry<'_>> {
445 let mut v: Vec<_> = iter_by_vis!(self, visibility).collect();
454 v.sort_by_key(|s| s.last_time());
455 if v.len() >= n {
456 let t = v.remove(v.len() - n);
457 Some(RepoEntry::new(t, &self.db_env))
458 } else {
459 None
460 }
461 }
462 pub fn get_mut(&mut self, name: &ScriptName, visibility: Visibility) -> Option<RepoEntry<'_>> {
463 let map = &mut self.map as *mut HashMap<String, ScriptInfo>;
465 let map = unsafe { &mut *map };
466 let key = name.key();
467 let info = match visibility {
468 Visibility::Normal => map.get_mut(&*key),
469 Visibility::Inverse => self.hidden_map.get_mut(&*key),
470 Visibility::All => {
471 let info = map.get_mut(&*key);
472 if info.is_some() {
474 info
475 } else {
476 self.hidden_map.get_mut(&*key)
477 }
478 }
479 };
480 let env = &self.db_env;
481 info.map(move |info| RepoEntry::new(info, env))
482 }
483 pub fn get_mut_by_id(&mut self, id: i64) -> Option<RepoEntry<'_>> {
484 self.iter_mut(Visibility::All).find(|e| e.id == id)
486 }
487
488 pub async fn remove(&mut self, id: i64) -> Result {
489 log::debug!("從資料庫刪除腳本 {:?}", id);
491 self.db_env.handle_delete(id).await?;
492 Ok(())
493 }
494 pub fn entry(&mut self, name: &ScriptName) -> RepoEntryOptional<'_> {
495 let entry = self.map.entry(name.key().into_owned());
497 RepoEntryOptional {
498 entry,
499 env: &self.db_env,
500 }
501 }
502}