249 lines
8.4 KiB
Rust
Executable File
249 lines
8.4 KiB
Rust
Executable File
#!/usr/bin/env runrs
|
|
|
|
//! ```cargo
|
|
//! [dependencies]
|
|
//! serde = { version = "1.0", features = ["derive"] }
|
|
//! serde_json = "1.0"
|
|
//! sha256 = "1.5"
|
|
//! rust_util = { version = "0.6" }
|
|
//! ```
|
|
|
|
use rust_util::{
|
|
debugging, failure_and_exit, iff, opt_result, opt_value_result, success, util_time, warning,
|
|
XResult,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::BTreeMap;
|
|
use std::fs;
|
|
use std::option::Option;
|
|
use std::path::PathBuf;
|
|
|
|
const SCRIPT_META_FILE: &str = "script-meta-v2.json";
|
|
const SCRIPT_CONFIG_FILE: &str = "script-config.json";
|
|
const SINGLE_SCRIPTS_DIR: &str = "single-scripts";
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ScriptConfig {
|
|
file_ext: String,
|
|
simple_script_url: String,
|
|
project_script_url: String,
|
|
script_sub_dir: Option<String>,
|
|
skip_dirs: Vec<String>,
|
|
}
|
|
|
|
impl ScriptConfig {
|
|
fn skip_dir(&self, dir: &str) -> bool {
|
|
for skip_dir in &self.skip_dirs {
|
|
if skip_dir == dir {
|
|
return true;
|
|
}
|
|
}
|
|
dir == "update-meta-rs" || dir == SINGLE_SCRIPTS_DIR || dir.starts_with(".")
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct ScriptMeta {
|
|
script_name: String,
|
|
script_length: u64,
|
|
script_sha256: String,
|
|
script_full_url: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
single_script_file: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
publish_time: Option<u128>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
update_time: Option<u128>,
|
|
}
|
|
|
|
fn main() -> XResult<()> {
|
|
if fs::metadata(SCRIPT_CONFIG_FILE).is_err() {
|
|
failure_and_exit!("Script config file {} not found.", SCRIPT_CONFIG_FILE);
|
|
}
|
|
if fs::metadata(SCRIPT_META_FILE).is_err() {
|
|
failure_and_exit!("Script meta file {} not found.", SCRIPT_META_FILE);
|
|
}
|
|
|
|
let script_config_content = opt_result!(
|
|
fs::read_to_string(SCRIPT_CONFIG_FILE),
|
|
"Read {}, failed: {}",
|
|
SCRIPT_CONFIG_FILE
|
|
);
|
|
let script_config: ScriptConfig = opt_result!(
|
|
serde_json::from_str(&script_config_content),
|
|
"Parse {}, failed: {}",
|
|
SCRIPT_CONFIG_FILE
|
|
);
|
|
debugging!("Script config: {:#?}", script_config);
|
|
|
|
let former_script_meta_map = read_script_meta_map_from_file();
|
|
let mut script_meta_map = BTreeMap::new();
|
|
|
|
let current_read_dir = opt_result!(fs::read_dir("."), "Read dir '.' failed: {}");
|
|
for dir in current_read_dir {
|
|
let dir_entry = opt_result!(dir, "Get dir failed: {}");
|
|
let dir_file_type = opt_result!(dir_entry.file_type(), "Get dir type failed: {}");
|
|
let file_name_os_string = dir_entry.file_name();
|
|
let script_dir = opt_value_result!(file_name_os_string.to_str(), "Cannot get file name.");
|
|
if !dir_file_type.is_dir() {
|
|
debugging!("Skip none dir: {}", script_dir);
|
|
continue;
|
|
}
|
|
if script_config.skip_dir(&script_dir) {
|
|
debugging!("Skip update skip dirs: {}", script_dir);
|
|
continue;
|
|
}
|
|
let abs_dir_entry = std::path::absolute(&dir_entry.path())?;
|
|
let mut main_script = abs_dir_entry;
|
|
if let Some(sub_dir) = &script_config.script_sub_dir {
|
|
main_script = main_script.join(sub_dir);
|
|
}
|
|
main_script = main_script.join(format!("main.{}", script_config.file_ext));
|
|
|
|
if let Some(script_file_name) =
|
|
translate_script_dir_to_script_name(script_dir, &script_config)
|
|
{
|
|
script_meta_map.insert(
|
|
script_file_name.clone(),
|
|
read_script_meta(
|
|
script_dir,
|
|
script_file_name.clone(),
|
|
&main_script,
|
|
false,
|
|
&script_config,
|
|
&former_script_meta_map,
|
|
)?,
|
|
);
|
|
}
|
|
}
|
|
|
|
if let Ok(single_script_meta) = fs::metadata(SINGLE_SCRIPTS_DIR) {
|
|
if single_script_meta.is_dir() {
|
|
let single_scripts_read_dir =
|
|
opt_result!(fs::read_dir(SINGLE_SCRIPTS_DIR), "Read dir '.' failed: {}");
|
|
for file in single_scripts_read_dir {
|
|
let file_entry = opt_result!(file, "Get dir failed: {}");
|
|
let dir_file_type = opt_result!(file_entry.file_type(), "Get dir type failed: {}");
|
|
let file_name_os_string = file_entry.file_name();
|
|
let script_file =
|
|
opt_value_result!(file_name_os_string.to_str(), "Cannot get file name.");
|
|
if !dir_file_type.is_file() {
|
|
debugging!("Skip none file: {}", script_file);
|
|
continue;
|
|
}
|
|
let abs_file_entry = std::path::absolute(&file_entry.path())?;
|
|
let script_file_ext = format!(".{}", script_config.file_ext);
|
|
if !script_file.ends_with(&script_file_ext) {
|
|
continue;
|
|
}
|
|
let script_file_name = script_file.to_string();
|
|
if script_meta_map.contains_key(&script_file_name) {
|
|
warning!("Script: {script_file_name} exists.");
|
|
continue;
|
|
}
|
|
script_meta_map.insert(
|
|
script_file_name.clone(),
|
|
read_script_meta(
|
|
"",
|
|
script_file_name.clone(),
|
|
&abs_file_entry,
|
|
true,
|
|
&script_config,
|
|
&former_script_meta_map,
|
|
)?,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
let script_meta_json = serde_json::to_string_pretty(&script_meta_map)?;
|
|
|
|
fs::write(SCRIPT_META_FILE, script_meta_json.as_bytes())?;
|
|
|
|
success!("Update file: {} succeed.", SCRIPT_META_FILE);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn read_script_meta_map_from_file() -> BTreeMap<String, ScriptMeta> {
|
|
let script_meta_content = match fs::read_to_string(SCRIPT_META_FILE) {
|
|
Ok(script_meta_content) => script_meta_content,
|
|
Err(_) => return BTreeMap::new(),
|
|
};
|
|
serde_json::from_str(&script_meta_content).unwrap_or_default()
|
|
}
|
|
|
|
// translate filename-ext to filename.ext
|
|
fn translate_script_dir_to_script_name(
|
|
script_dir: &str,
|
|
script_config: &ScriptConfig,
|
|
) -> Option<String> {
|
|
let script_dir_ext = format!("-{}", script_config.file_ext);
|
|
if script_dir.ends_with(&script_dir_ext) {
|
|
let remove_ext_dir_name = script_dir
|
|
.chars()
|
|
.take(script_dir.len() - script_dir_ext.len())
|
|
.collect::<String>();
|
|
Some(format!("{remove_ext_dir_name}.{}", script_config.file_ext))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn read_script_meta(
|
|
script_dir: &str,
|
|
script_name: String,
|
|
script_path: &PathBuf,
|
|
is_simple_script: bool,
|
|
script_config: &ScriptConfig,
|
|
former_script_meta_map: &BTreeMap<String, ScriptMeta>,
|
|
) -> XResult<ScriptMeta> {
|
|
let script_meta = opt_result!(
|
|
script_path.metadata(),
|
|
"Read file: {:?} meta failed: {}",
|
|
script_path
|
|
);
|
|
let script_path_content = fs::read(script_path)?;
|
|
let script_sha256 = sha256::digest(&script_path_content);
|
|
let script_full_url = if is_simple_script {
|
|
script_config
|
|
.simple_script_url
|
|
.replace("$NAME", &script_name)
|
|
} else {
|
|
script_config
|
|
.project_script_url
|
|
.replace("$NAME", script_dir)
|
|
};
|
|
let single_script_file = iff!(is_simple_script, Some(true), None);
|
|
let former_script_meta = former_script_meta_map.get(&script_name);
|
|
let publish_time = Some(
|
|
former_script_meta
|
|
.map(|m| m.publish_time)
|
|
.flatten()
|
|
.unwrap_or_else(|| util_time::get_current_millis()),
|
|
);
|
|
let is_file_same = former_script_meta
|
|
.map(|m| m.script_sha256 == script_sha256)
|
|
.unwrap_or(false);
|
|
let update_time = Some(if is_file_same {
|
|
former_script_meta
|
|
.map(|m| m.update_time)
|
|
.flatten()
|
|
.unwrap_or_else(|| util_time::get_current_millis())
|
|
} else {
|
|
util_time::get_current_millis()
|
|
});
|
|
Ok(ScriptMeta {
|
|
script_name: script_name.clone(),
|
|
script_length: script_meta.len(),
|
|
script_sha256,
|
|
script_full_url,
|
|
single_script_file,
|
|
publish_time,
|
|
update_time,
|
|
})
|
|
}
|
|
|
|
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20250124T004356+08:00.MEUCIHzuiug3f0EvwhN16ROH
|
|
// 0gs+3pe0YnclC7T+R3IW9qyOAiEAjit5sgfdlBMrqHVZCMgDy90cacw5TDNQ+z4xIKDYSW0=
|