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