1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
use crate::config::Config;
use crate::error::{Contextable, Error, FormatCode::ScriptName as ScriptNameCode, Result};
use crate::script_time::ScriptTime;
use crate::script_type::ScriptType;
use crate::tag::Tag;
use crate::tag::TagFilter;
use crate::util::illegal_name;
use chrono::NaiveDateTime;
use fxhash::FxHashSet as HashSet;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::cmp::Ordering;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::str::FromStr;

pub const ANONYMOUS: &str = ".anonymous";

macro_rules! max {
    ($x:expr) => ( $x );
    ($x:expr, $($xs:expr),+) => {
        {
            use std::cmp::max;
            max($x, max!( $($xs),+ ))
        }
    };
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ScriptName {
    Anonymous(u32),
    Named(String),
}
impl FromStr for ScriptName {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self> {
        s.to_owned().into_script_name()
    }
}
impl ScriptName {
    #[inline]
    pub fn valid(mut s: &str, fuzzing: bool, check_legal_name: bool) -> Result<Option<u32>> {
        log::debug!("檢查腳本名:{}", s);
        let reg = regex::Regex::new(r"^\.(\d+)$")?;
        let m = reg.captures(s);
        if let Some(m) = m {
            let id_str = m.get(1).unwrap().as_str();
            match id_str.parse::<u32>() {
                Ok(id) => Ok(Some(id)),
                Err(e) => Err(Error::Format(ScriptNameCode, s.to_owned())).context(e),
            }
        } else {
            if fuzzing {
                if s == "." {
                    log::info!("特殊規則:模糊搜時允許單一個`.`");
                    return Ok(None); // NOTE: 讓匿名腳本可以直接用 `.` 來搜
                } else if s.ends_with('/') {
                    log::info!("特殊規則:模糊搜時允許以`/`結尾");
                    s = &s[..s.len() - 1]; // NOTE: 有了補全,很容易補出帶著`/`結尾的指令,放寬標準吧
                }
            }
            // FIXME: 好好想想什麼樣的腳本名可行,並補上單元測試
            if check_legal_name {
                for s in s.split('/') {
                    if illegal_name(s) {
                        return Err(Error::Format(ScriptNameCode, s.to_owned()));
                    }
                }
            }
            Ok(None)
        }
    }
    pub fn namespaces(&self) -> Vec<&'_ str> {
        match self {
            ScriptName::Anonymous(_) => vec![],
            ScriptName::Named(s) => {
                let mut v: Vec<_> = s.split('/').collect();
                v.pop();
                v
            }
        }
    }
    pub fn is_anonymous(&self) -> bool {
        matches!(self, ScriptName::Anonymous(_))
    }
    pub fn key(&self) -> Cow<'_, str> {
        match self {
            ScriptName::Anonymous(id) => Cow::Owned(format!(".{}", id)),
            ScriptName::Named(s) => Cow::Borrowed(s),
        }
    }
    /// 回傳值是相對於 HS_HOME 的路徑
    pub fn to_file_path(&self, ty: &ScriptType) -> Result<PathBuf> {
        self.to_file_path_inner(ty, false)
    }
    /// 回傳值是相對於 HS_HOME 的路徑,對未知的類別直接用類別名作擴展名
    pub fn to_file_path_fallback(&self, ty: &ScriptType) -> PathBuf {
        self.to_file_path_inner(ty, true).unwrap()
    }
    fn to_file_path_inner(&self, ty: &ScriptType, fallback: bool) -> Result<PathBuf> {
        fn add_ext(name: &mut String, ty: &ScriptType, fallback: bool) -> Result {
            let ext = match Config::get().get_script_conf(ty) {
                Err(e) => {
                    if !fallback {
                        return Err(e);
                    }
                    log::warn!(
                        "取腳本路徑時找不到類別設定:{},直接把類別名當擴展名試試",
                        e,
                    );
                    Some(ty.as_ref())
                }
                Ok(c) => c.ext.as_ref().map(|s| s.as_ref()),
            };
            if let Some(ext) = ext {
                *name = format!("{}.{}", name, ext);
            }
            Ok(())
        }
        match self {
            ScriptName::Anonymous(id) => {
                let mut file_name = id.to_string();
                let dir: PathBuf = ANONYMOUS.into();
                add_ext(&mut file_name, ty, fallback)?;
                Ok(dir.join(file_name))
            }
            ScriptName::Named(name) => {
                let mut file_name = name.to_string();
                add_ext(&mut file_name, ty, fallback)?;
                Ok(file_name.into())
            }
        }
    }
}
impl PartialOrd for ScriptName {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (self, other) {
            (ScriptName::Named(n1), ScriptName::Named(n2)) => n1.partial_cmp(n2),
            (ScriptName::Anonymous(i1), ScriptName::Anonymous(i2)) => i1.partial_cmp(i2),
            (ScriptName::Named(_), ScriptName::Anonymous(_)) => Some(Ordering::Less),
            (ScriptName::Anonymous(_), ScriptName::Named(_)) => Some(Ordering::Greater),
        }
    }
}
impl Ord for ScriptName {
    fn cmp(&self, other: &Self) -> Ordering {
        self.partial_cmp(other).unwrap()
    }
}
impl std::fmt::Display for ScriptName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.key())
    }
}

#[derive(Debug, Clone)]
pub struct TimelessScriptInfo {
    pub changed: bool,
    pub id: i64,
    pub name: ScriptName,
    pub tags: HashSet<Tag>,
    pub ty: ScriptType,
    pub created_time: ScriptTime,
}
#[derive(Debug, Deref, Clone)]
pub struct ScriptInfo {
    pub humble_time: Option<NaiveDateTime>,
    pub read_time: ScriptTime,
    pub write_time: ScriptTime,
    pub miss_time: Option<ScriptTime>,
    pub neglect_time: Option<ScriptTime>,
    /// (content, args, dir)
    pub exec_time: Option<ScriptTime<(String, String, Option<PathBuf>)>>,
    /// (return code, main event id)
    pub exec_done_time: Option<ScriptTime<(i32, i64)>>,
    pub exec_count: u64,
    #[deref]
    /// 用來區隔「時間資料」和「其它元資料」,並偵測其它元資料的修改
    timeless_info: TimelessScriptInfo,
}
impl DerefMut for ScriptInfo {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.timeless_info.changed = true;
        &mut self.timeless_info
    }
}

fn map<T: Deref<Target = NaiveDateTime>>(time: &Option<T>) -> NaiveDateTime {
    match time {
        Some(time) => **time,
        _ => NaiveDateTime::from_timestamp(1, 0),
    }
}
impl ScriptInfo {
    pub fn set_id(&mut self, id: i64) {
        assert_eq!(self.id, 0, "只有 id=0(代表新腳本)時可以設定 id");
        self.timeless_info.id = id;
    }
    pub fn append_tags(&mut self, tags: TagFilter) {
        if tags.append {
            log::debug!("附加上標籤:{:?}", tags);
            tags.fill_allowed_map(&mut self.tags);
        } else {
            log::debug!("設定標籤:{:?}", tags);
            self.tags = tags.into_allowed_iter().collect();
        }
    }
    pub fn cp(&self, new_name: ScriptName) -> Self {
        let builder = ScriptInfo::builder(0, new_name, self.ty.clone(), self.tags.iter().cloned());
        builder.build()
    }
    /// `major time` 即不包含 `read` 和 `miss` 事件的時間,但包含 `humble`
    pub fn last_major_time(&self) -> NaiveDateTime {
        max!(
            *self.write_time,
            map(&self.humble_time.as_ref()),
            map(&self.exec_time),
            map(&self.exec_done_time)
        )
    }
    /// 不包含 `humble`
    pub fn last_time(&self) -> NaiveDateTime {
        max!(
            *self.read_time,
            *self.write_time,
            map(&self.miss_time),
            map(&self.exec_time),
            map(&self.exec_done_time)
        )
    }
    pub fn file_path_fallback(&self) -> PathBuf {
        self.name.to_file_path_fallback(&self.ty)
    }
    pub fn read(&mut self) {
        self.read_time = ScriptTime::now(());
    }
    pub fn write(&mut self) {
        let now = ScriptTime::now(());
        self.read_time = now.clone();
        self.write_time = now;
    }
    pub fn miss(&mut self) {
        self.miss_time = Some(ScriptTime::now(()));
    }
    pub fn exec(&mut self, content: String, args: &[String], dir: Option<PathBuf>) {
        log::trace!("{:?} 執行內容為 {}", self, content);
        let args_ser = serde_json::to_string(args).unwrap();
        self.exec_time = Some(ScriptTime::now((content, args_ser, dir)));
        // NOTE: no readtime, otherwise it will be hard to tell what event was caused by what operation.
        self.exec_count += 1;
    }
    pub fn exec_done(&mut self, code: i32, main_event_id: i64) {
        log::trace!("{:?} 執行結果為 {}", self, code);
        self.exec_done_time = Some(ScriptTime::now((code, main_event_id)));
    }
    pub fn neglect(&mut self) {
        self.neglect_time = Some(ScriptTime::now(()))
    }
    pub fn builder(
        id: i64,
        name: ScriptName,
        ty: ScriptType,
        tags: impl Iterator<Item = Tag>,
    ) -> ScriptBuilder {
        ScriptBuilder {
            id,
            name,
            ty,
            tags: tags.collect(),
            read_time: None,
            miss_time: None,
            created_time: None,
            exec_time: None,
            write_time: None,
            exec_done_time: None,
            neglect_time: None,
            humble_time: None,
            exec_count: 0,
        }
    }
}

pub trait IntoScriptName {
    fn into_script_name(self) -> Result<ScriptName>;
    fn into_script_name_unchecked(self) -> Result<ScriptName>
    where
        Self: Sized,
    {
        self.into_script_name()
    }
}

impl IntoScriptName for u32 {
    fn into_script_name(self) -> Result<ScriptName> {
        Ok(ScriptName::Anonymous(self))
    }
}
#[inline]
fn string_into_script_name(s: String, check_legal_name: bool) -> Result<ScriptName> {
    log::debug!("解析腳本名:{} {}", s, check_legal_name);
    if let Some(id) = ScriptName::valid(&s, false, check_legal_name)? {
        id.into_script_name()
    } else {
        Ok(ScriptName::Named(s))
    }
}
impl IntoScriptName for String {
    fn into_script_name(self) -> Result<ScriptName> {
        string_into_script_name(self, true)
    }
    fn into_script_name_unchecked(self) -> Result<ScriptName> {
        string_into_script_name(self, false)
    }
}
impl IntoScriptName for ScriptName {
    fn into_script_name(self) -> Result<ScriptName> {
        Ok(self)
    }
}

#[derive(Debug)]
pub struct ScriptBuilder {
    pub name: ScriptName,
    read_time: Option<NaiveDateTime>,
    miss_time: Option<NaiveDateTime>,
    created_time: Option<NaiveDateTime>,
    write_time: Option<NaiveDateTime>,
    exec_time: Option<NaiveDateTime>,
    neglect_time: Option<NaiveDateTime>,
    humble_time: Option<NaiveDateTime>,
    exec_done_time: Option<NaiveDateTime>,
    exec_count: u64,
    id: i64,
    tags: HashSet<Tag>,
    ty: ScriptType,
}

impl ScriptBuilder {
    pub fn exec_count(&mut self, count: u64) -> &mut Self {
        self.exec_count = count;
        self
    }
    pub fn exec_time(&mut self, time: NaiveDateTime) -> &mut Self {
        self.exec_time = Some(time);
        self
    }
    pub fn exec_done_time(&mut self, time: NaiveDateTime) -> &mut Self {
        self.exec_done_time = Some(time);
        self
    }
    pub fn read_time(&mut self, time: NaiveDateTime) -> &mut Self {
        self.read_time = Some(time);
        self
    }
    pub fn miss_time(&mut self, time: NaiveDateTime) -> &mut Self {
        self.miss_time = Some(time);
        self
    }
    pub fn write_time(&mut self, time: NaiveDateTime) -> &mut Self {
        self.write_time = Some(time);
        self
    }
    pub fn neglect_time(&mut self, time: NaiveDateTime) -> &mut Self {
        self.neglect_time = Some(time);
        self
    }
    pub fn humble_time(&mut self, time: NaiveDateTime) -> &mut Self {
        self.humble_time = Some(time);
        self
    }
    pub fn created_time(&mut self, time: NaiveDateTime) -> &mut Self {
        self.created_time = Some(time);
        self
    }
    pub fn build(self) -> ScriptInfo {
        let created_time = ScriptTime::new_or_else(self.created_time, || ScriptTime::now(()));
        ScriptInfo {
            write_time: ScriptTime::new_or(self.write_time, created_time.clone()),
            read_time: ScriptTime::new_or(self.read_time, created_time.clone()),
            miss_time: self.miss_time.map(ScriptTime::new),
            exec_time: self.exec_time.map(ScriptTime::new),
            exec_done_time: self.exec_done_time.map(ScriptTime::new),
            neglect_time: self.neglect_time.map(ScriptTime::new),
            humble_time: self.humble_time,
            exec_count: self.exec_count,
            timeless_info: TimelessScriptInfo {
                changed: false,
                id: self.id,
                name: self.name,
                ty: self.ty,
                tags: self.tags,
                created_time,
            },
        }
    }
}