hololake-system-architecture/product-source/guanghu-education-industry-os/src-tauri/src/lib.rs

149 lines
5.4 KiB
Rust

use serde::Serialize;
use serde_json::Value;
use std::io::Write;
use std::process::{Command, Stdio};
const NODE_ALIAS: &str = "xx-gz-001";
const CHANNEL_ROOT: &str = "/var/lib/guanghu/education-data/channels/GH-EDU-CHANNEL-XX-001";
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ChannelSnapshot {
node: Value,
fifth_domain_link: Value,
module_registry: Value,
update_authority: Value,
channel: Value,
code_channel: Value,
}
fn ssh_output(remote_command: &str) -> Result<String, String> {
let output = Command::new("/usr/bin/ssh")
.args([
"-o",
"BatchMode=yes",
"-o",
"ConnectTimeout=10",
NODE_ALIAS,
remote_command,
])
.output()
.map_err(|error| format!("SSH_LAUNCH_FAILED:{error}"))?;
if !output.status.success() {
return Err(format!(
"SSH_REMOTE_FAILED:{}",
String::from_utf8_lossy(&output.stderr).trim()
));
}
String::from_utf8(output.stdout).map_err(|error| format!("SSH_OUTPUT_INVALID_UTF8:{error}"))
}
fn read_remote_json(path: &str) -> Result<Value, String> {
let payload = ssh_output(&format!("cat {path}"))?;
serde_json::from_str(&payload).map_err(|error| format!("REMOTE_JSON_INVALID:{path}:{error}"))
}
#[tauri::command]
fn connect_channel() -> Result<ChannelSnapshot, String> {
let code_version = ssh_output("curl -fsS http://127.0.0.1:3901/api/v1/version")?;
let repository_search =
ssh_output("curl -fsS 'http://127.0.0.1:3901/api/v1/repos/search?limit=20'")?;
Ok(ChannelSnapshot {
node: read_remote_json("/var/lib/guanghu/protocol/node.json")?,
fifth_domain_link: read_remote_json(
"/var/lib/guanghu/receipts/XX-GZ-001/FIFTH-DOMAIN-LINK-CURRENT.json",
)?,
module_registry: read_remote_json("/opt/guanghu/protocol/current/module-registry.json")?,
update_authority: read_remote_json("/opt/guanghu/protocol/current/update-authority.json")?,
channel: read_remote_json("/opt/guanghu/protocol/current/xiaoxin-channel.json")?,
code_channel: serde_json::json!({
"version": serde_json::from_str::<Value>(&code_version)
.map_err(|error| format!("CODE_CHANNEL_VERSION_INVALID:{error}"))?,
"repositories": serde_json::from_str::<Value>(&repository_search)
.map_err(|error| format!("CODE_CHANNEL_REPOSITORIES_INVALID:{error}"))?
}),
})
}
fn channel_file(kind: &str) -> Result<(&'static str, &'static str), String> {
match kind {
"document" => Ok(("teacher-training-plan.md", "GH-EDU-OFFICE-DOC-001")),
"sheet" => Ok(("teacher-training-data.csv", "GH-EDU-OFFICE-SHEET-001")),
_ => Err(format!("UNREGISTERED_CHANNEL_FILE:{kind}")),
}
}
#[tauri::command]
fn read_channel_file(kind: String) -> Result<String, String> {
let (filename, _) = channel_file(&kind)?;
ssh_output(&format!(
"path='{CHANNEL_ROOT}/documents/{filename}'; if [ -f \"$path\" ]; then cat \"$path\"; fi"
))
}
#[tauri::command]
fn save_channel_file(kind: String, content: String) -> Result<Value, String> {
let (filename, module_id) = channel_file(&kind)?;
let path = format!("{CHANNEL_ROOT}/documents/{filename}");
let receipt = format!("{CHANNEL_ROOT}/receipts/{kind}-CURRENT.json");
let remote_command = format!(
"set -eu; \
install -d -m 700 '{CHANNEL_ROOT}/documents' '{CHANNEL_ROOT}/receipts'; \
path='{path}'; tmp=\"${{path}}.tmp.$$\"; umask 077; cat > \"$tmp\"; mv \"$tmp\" \"$path\"; \
sha=$(sha256sum \"$path\" | awk '{{print $1}}'); \
now=$(date -u +%Y-%m-%dT%H:%M:%SZ); \
receipt='{receipt}'; rtmp=\"${{receipt}}.tmp.$$\"; \
printf '{{\"schema\":\"guanghu.channel-write-receipt/v1\",\"channelId\":\"GH-EDU-CHANNEL-XX-001\",\"nodeId\":\"XX-GZ-001\",\"moduleId\":\"{module_id}\",\"kind\":\"{kind}\",\"sha256\":\"%s\",\"writtenAt\":\"%s\",\"state\":\"PASS_100\"}}\\n' \"$sha\" \"$now\" > \"$rtmp\"; \
mv \"$rtmp\" \"$receipt\"; cat \"$receipt\""
);
let mut child = Command::new("/usr/bin/ssh")
.args([
"-o",
"BatchMode=yes",
"-o",
"ConnectTimeout=10",
NODE_ALIAS,
&remote_command,
])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|error| format!("SSH_SAVE_LAUNCH_FAILED:{error}"))?;
child
.stdin
.take()
.ok_or_else(|| "SSH_SAVE_STDIN_UNAVAILABLE".to_string())?
.write_all(content.as_bytes())
.map_err(|error| format!("SSH_SAVE_WRITE_FAILED:{error}"))?;
let output = child
.wait_with_output()
.map_err(|error| format!("SSH_SAVE_WAIT_FAILED:{error}"))?;
if !output.status.success() {
return Err(format!(
"SSH_SAVE_REMOTE_FAILED:{}",
String::from_utf8_lossy(&output.stderr).trim()
));
}
serde_json::from_slice(&output.stdout)
.map_err(|error| format!("SSH_SAVE_RECEIPT_INVALID:{error}"))
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
connect_channel,
read_channel_file,
save_channel_file
])
.run(tauri::generate_context!())
.expect("failed to run Guanghu Education Subdomain OS");
}