1use crate::config::Config;
2use crate::error::{
3 Contextable, DisplayError, DisplayResult, Error, FormatCode::ScriptName as ScriptNameCode,
4 Result,
5};
6use crate::script_time::ScriptTime;
7use crate::script_type::ScriptType;
8use crate::tag::{Tag, TagSelector};
9use crate::util::illegal_name;
10use chrono::NaiveDateTime;
11use fxhash::FxHashSet as HashSet;
12use std::borrow::Cow;
13use std::cmp::Ordering;
14use std::fmt::Write;
15use std::ops::{Deref, DerefMut};
16use std::path::PathBuf;
17use std::str::FromStr;
18
19pub const ANONYMOUS: &str = ".anonymous";
20
21macro_rules! max {
22 ($x:expr) => ( $x );
23 ($x:expr, $($xs:expr),+) => {
24 {
25 use std::cmp::max;
26 max($x, max!( $($xs),+ ))
27 }
28 };
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Deref, Display, Hash)]
32#[display(fmt = "{}", inner)]
33pub struct ConcreteScriptName {
34 #[deref]
35 inner: String,
36}
37impl ConcreteScriptName {
38 fn valid(s: &str) -> Result {
39 for s in s.split('/') {
41 if illegal_name(s) {
42 return ScriptNameCode.to_res(s.to_owned());
43 }
44 }
45 Ok(())
46 }
47 pub fn new(s: String) -> Result<Self> {
48 Self::valid(&s)?;
49 Ok(ConcreteScriptName { inner: s })
50 }
51 pub fn new_id(id: u32) -> Self {
52 ConcreteScriptName {
53 inner: id.to_string(),
54 }
55 }
56 fn new_unchecked(s: String) -> Self {
57 ConcreteScriptName { inner: s }
58 }
59 fn stem_inner(&self) -> &str {
60 if let Some((_, stem)) = self.inner.rsplit_once('/') {
61 stem
62 } else {
63 &self.inner
64 }
65 }
66 pub fn stem(&self) -> ConcreteScriptName {
67 Self::new_unchecked(self.stem_inner().to_owned())
68 }
69 pub fn join(&mut self, other: &ConcreteScriptName) {
70 write!(&mut self.inner, "/{}", other.stem_inner()).unwrap();
71 }
72 pub fn join_id(&mut self, other: u32) {
73 write!(&mut self.inner, "/{}", other).unwrap();
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Hash)]
78pub enum ScriptName {
79 Anonymous(u32),
80 Named(ConcreteScriptName),
81}
82impl FromStr for ScriptName {
95 type Err = DisplayError;
96 fn from_str(s: &str) -> DisplayResult<Self> {
97 let n = s.to_owned().into_script_name()?;
98 Ok(n)
99 }
100}
101impl ScriptName {
102 #[inline]
103 pub fn valid(
104 mut s: &str,
105 allow_endwith_slash: bool,
106 allow_dot: bool,
107 check: bool,
108 ) -> Result<Option<u32>> {
109 log::debug!("檢查腳本名:{}", s);
110
111 if s.starts_with('.') {
112 if s.len() == 1 && allow_dot {
113 log::info!("特殊規則:允許單一個`.`");
114 return Ok(None); }
116 match s[1..].parse::<std::num::NonZeroU32>() {
117 Ok(id) => Ok(Some(id.get())),
118 Err(e) => ScriptNameCode.to_res(s.to_owned()).context(e),
119 }
120 } else if check {
121 if s.ends_with('/') && allow_endwith_slash {
122 log::info!("特殊規則:允許以`/`結尾");
123 s = &s[..s.len() - 1]; }
125 ConcreteScriptName::valid(&s)?;
126 Ok(None)
127 } else {
128 Ok(None)
129 }
130 }
131 pub fn namespaces(&self) -> Vec<&'_ str> {
132 match self {
133 ScriptName::Anonymous(_) => vec![],
134 ScriptName::Named(s) => {
135 let mut v: Vec<_> = s.split('/').collect();
136 v.pop();
137 v
138 }
139 }
140 }
141 pub fn is_anonymous(&self) -> bool {
142 log::debug!("判斷是否為匿名:{:?}", self);
143 matches!(self, ScriptName::Anonymous(_))
144 }
145 pub fn key(&self) -> Cow<'_, str> {
146 match self {
147 ScriptName::Anonymous(id) => Cow::Owned(format!(".{}", id)),
148 ScriptName::Named(s) => Cow::Borrowed(s),
149 }
150 }
151 pub fn to_file_path(&self, ty: &ScriptType) -> Result<PathBuf> {
153 self.to_file_path_inner(ty, false).map(|t| t.0)
154 }
155 pub fn to_file_path_fallback(&self, ty: &ScriptType) -> (PathBuf, Option<Error>) {
157 self.to_file_path_inner(ty, true).unwrap()
158 }
159 fn to_file_path_inner(
160 &self,
161 ty: &ScriptType,
162 fallback: bool,
163 ) -> Result<(PathBuf, Option<Error>)> {
164 fn add_ext(
165 name: &mut String,
166 ty: &ScriptType,
167 fallback: bool,
168 err: &mut Option<Error>,
169 ) -> Result {
170 let ext = match Config::get().get_script_conf(ty) {
171 Err(e) => {
172 if !fallback {
173 return Err(e);
174 }
175 log::warn!(
176 "取腳本路徑時找不到類別設定:{},直接把類別名當擴展名試試",
177 e,
178 );
179 *err = Some(e);
180 Some(ty.as_ref())
181 }
182 Ok(c) => c.ext.as_ref().map(|s| s.as_ref()),
183 };
184 if let Some(ext) = ext {
185 write!(name, ".{}", ext).unwrap();
186 }
187 Ok(())
188 }
189 let mut err = None;
190 match self {
191 ScriptName::Anonymous(id) => {
192 let mut file_name = id.to_string();
193 let dir: PathBuf = ANONYMOUS.into();
194 add_ext(&mut file_name, ty, fallback, &mut err)?;
195 Ok((dir.join(file_name), err))
196 }
197 ScriptName::Named(name) => {
198 let mut file_name = name.to_string();
199 add_ext(&mut file_name, ty, fallback, &mut err)?;
200 Ok((file_name.into(), err))
201 }
202 }
203 }
204}
205impl PartialOrd for ScriptName {
206 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
207 match (self, other) {
208 (ScriptName::Named(n1), ScriptName::Named(n2)) => n1.inner.partial_cmp(&n2.inner),
209 (ScriptName::Anonymous(i1), ScriptName::Anonymous(i2)) => i1.partial_cmp(i2),
210 (ScriptName::Named(_), ScriptName::Anonymous(_)) => Some(Ordering::Less),
211 (ScriptName::Anonymous(_), ScriptName::Named(_)) => Some(Ordering::Greater),
212 }
213 }
214}
215impl Ord for ScriptName {
216 fn cmp(&self, other: &Self) -> Ordering {
217 self.partial_cmp(other).unwrap()
218 }
219}
220impl std::fmt::Display for ScriptName {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 write!(f, "{}", self.key())
223 }
224}
225
226#[derive(Debug, Clone)]
227pub struct TimelessScriptInfo {
228 pub changed: bool,
229 pub id: i64,
230 pub hash: i64,
231 pub name: ScriptName,
232 pub tags: HashSet<Tag>,
233 pub ty: ScriptType,
234 pub created_time: ScriptTime,
235}
236#[derive(Debug, Deref, Clone)]
237pub struct ScriptInfo {
238 pub humble_time: Option<NaiveDateTime>,
239 pub read_time: ScriptTime,
240 pub write_time: ScriptTime,
241 pub neglect_time: Option<ScriptTime>,
242 pub exec_time: Option<ScriptTime<(String, String, String, Option<PathBuf>)>>,
244 pub exec_done_time: Option<ScriptTime<(i32, i64)>>,
246 pub exec_count: u64,
247 #[deref]
248 timeless_info: TimelessScriptInfo,
250}
251impl DerefMut for ScriptInfo {
252 fn deref_mut(&mut self) -> &mut Self::Target {
253 self.timeless_info.changed = true;
254 &mut self.timeless_info
255 }
256}
257
258fn map<T: Deref<Target = NaiveDateTime>>(time: &Option<T>) -> NaiveDateTime {
259 match time {
260 Some(time) => **time,
261 _ => Default::default(),
262 }
263}
264impl ScriptInfo {
265 pub fn set_id(&mut self, id: i64) {
266 assert_eq!(self.id, 0, "只有 id=0(代表新腳本)時可以設定 id");
267 self.timeless_info.id = id;
268 }
269 pub fn append_tags(&mut self, tags: TagSelector) {
270 if tags.append {
271 log::debug!("附加上標籤:{:?}", tags);
272 tags.fill_allowed_map(&mut self.tags);
273 } else {
274 log::debug!("設定標籤:{:?}", tags);
275 self.tags = tags.into_allowed_iter().collect();
276 }
277 }
278 pub fn cp(&self, new_name: ScriptName) -> Self {
279 let builder = ScriptInfo::builder(
280 0,
281 self.hash,
282 new_name,
283 self.ty.clone(),
284 self.tags.iter().cloned(),
285 );
286 builder.build()
287 }
288 pub fn last_major_time(&self) -> NaiveDateTime {
290 max!(
291 *self.write_time,
292 map(&self.humble_time.as_ref()),
293 map(&self.exec_time),
294 map(&self.exec_done_time)
295 )
296 }
297 pub fn last_time(&self) -> NaiveDateTime {
299 max!(
300 *self.read_time,
301 *self.write_time,
302 map(&self.exec_time),
303 map(&self.exec_done_time)
304 )
305 }
306 pub fn file_path_fallback(&self) -> PathBuf {
307 self.name.to_file_path_fallback(&self.ty).0
308 }
309 pub fn read(&mut self) {
310 self.read_time = ScriptTime::now(());
311 }
312 pub fn write(&mut self) {
313 let now = ScriptTime::now(());
314 self.read_time = now.clone();
315 self.write_time = now;
316 }
317 pub fn exec(
318 &mut self,
319 content: String,
320 args: &[String],
321 env_record: String,
322 dir: Option<PathBuf>,
323 ) {
324 log::trace!("{:?} 執行內容為 {}", self, content);
325 let args_ser = serde_json::to_string(args).unwrap();
326 self.exec_time = Some(ScriptTime::now((content, args_ser, env_record, dir)));
327 self.exec_count += 1;
329 }
330 pub fn exec_done(&mut self, code: i32, main_event_id: i64) {
331 log::trace!("{:?} 執行結果為 {}", self, code);
332 self.exec_done_time = Some(ScriptTime::now((code, main_event_id)));
333 }
334 pub fn neglect(&mut self) {
335 self.neglect_time = Some(ScriptTime::now(()))
336 }
337 pub fn builder(
338 id: i64,
339 hash: i64,
340 name: ScriptName,
341 ty: ScriptType,
342 tags: impl Iterator<Item = Tag>,
343 ) -> ScriptBuilder {
344 ScriptBuilder {
345 id,
346 hash,
347 name,
348 ty,
349 tags: tags.collect(),
350 read_time: None,
351 created_time: None,
352 exec_time: None,
353 write_time: None,
354 exec_done_time: None,
355 neglect_time: None,
356 humble_time: None,
357 exec_count: 0,
358 }
359 }
360}
361
362pub trait IntoScriptName {
363 fn into_script_name(self) -> Result<ScriptName>;
364 fn into_script_name_unchecked(self) -> Result<ScriptName>
365 where
366 Self: Sized,
367 {
368 self.into_script_name()
369 }
370}
371
372impl IntoScriptName for u32 {
373 fn into_script_name(self) -> Result<ScriptName> {
374 Ok(ScriptName::Anonymous(self))
375 }
376}
377impl IntoScriptName for ConcreteScriptName {
378 fn into_script_name(self) -> Result<ScriptName> {
379 Ok(ScriptName::Named(self))
380 }
381}
382#[inline]
383fn string_into_script_name(s: String, check: bool) -> Result<ScriptName> {
384 log::debug!("解析腳本名:{} {}", s, check);
385 if let Some(id) = ScriptName::valid(&s, false, false, check)? {
386 id.into_script_name()
387 } else {
388 Ok(ScriptName::Named(ConcreteScriptName::new_unchecked(s))) }
390}
391impl IntoScriptName for String {
392 fn into_script_name(self) -> Result<ScriptName> {
393 string_into_script_name(self, true)
394 }
395 fn into_script_name_unchecked(self) -> Result<ScriptName> {
396 string_into_script_name(self, false)
397 }
398}
399impl IntoScriptName for ScriptName {
400 fn into_script_name(self) -> Result<ScriptName> {
401 Ok(self)
402 }
403}
404
405#[derive(Debug)]
406pub struct ScriptBuilder {
407 pub name: ScriptName,
408 read_time: Option<NaiveDateTime>,
409 created_time: Option<NaiveDateTime>,
410 write_time: Option<NaiveDateTime>,
411 exec_time: Option<NaiveDateTime>,
412 neglect_time: Option<NaiveDateTime>,
413 humble_time: Option<NaiveDateTime>,
414 exec_done_time: Option<NaiveDateTime>,
415 exec_count: u64,
416
417 hash: i64,
418 id: i64,
419 tags: HashSet<Tag>,
420 ty: ScriptType,
421}
422
423impl ScriptBuilder {
424 pub fn exec_count(&mut self, count: u64) -> &mut Self {
425 self.exec_count = count;
426 self
427 }
428 pub fn exec_time(&mut self, time: NaiveDateTime) -> &mut Self {
429 self.exec_time = Some(time);
430 self
431 }
432 pub fn exec_done_time(&mut self, time: NaiveDateTime) -> &mut Self {
433 self.exec_done_time = Some(time);
434 self
435 }
436 pub fn read_time(&mut self, time: NaiveDateTime) -> &mut Self {
437 self.read_time = Some(time);
438 self
439 }
440 pub fn write_time(&mut self, time: NaiveDateTime) -> &mut Self {
441 self.write_time = Some(time);
442 self
443 }
444 pub fn neglect_time(&mut self, time: NaiveDateTime) -> &mut Self {
445 self.neglect_time = Some(time);
446 self
447 }
448 pub fn humble_time(&mut self, time: NaiveDateTime) -> &mut Self {
449 self.humble_time = Some(time);
450 self
451 }
452 pub fn created_time(&mut self, time: NaiveDateTime) -> &mut Self {
453 self.created_time = Some(time);
454 self
455 }
456 pub fn build(self) -> ScriptInfo {
457 let created_time = ScriptTime::new_or(self.created_time, ScriptTime::now(()));
458 ScriptInfo {
459 write_time: ScriptTime::new_or(self.write_time, created_time),
460 read_time: ScriptTime::new_or(self.read_time, created_time),
461 exec_time: self.exec_time.map(ScriptTime::new),
462 exec_done_time: self.exec_done_time.map(ScriptTime::new),
463 neglect_time: self.neglect_time.map(ScriptTime::new),
464 humble_time: self.humble_time,
465 exec_count: self.exec_count,
466 timeless_info: TimelessScriptInfo {
467 changed: false,
468 id: self.id,
469 hash: self.hash,
470 name: self.name,
471 ty: self.ty,
472 tags: self.tags,
473 created_time,
474 },
475 }
476 }
477}