From 0378191783e3290f6fcd1f8b4d4b72dcc4fa79c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A9=E5=98=89=E4=B9=90?= Date: Fri, 22 May 2026 01:54:43 +0800 Subject: [PATCH] feat: add the management of config_store_snapshot (#2271) --- easytier-contrib/easytier-ohrs/Cargo.lock | 77 +++++--- .../src/config/storage/config_meta.rs | 179 +++++++++++++++++- .../easytier-ohrs/src/exports/config_api.rs | 8 + easytier-contrib/easytier-ohrs/src/lib.rs | 10 + 4 files changed, 249 insertions(+), 25 deletions(-) diff --git a/easytier-contrib/easytier-ohrs/Cargo.lock b/easytier-contrib/easytier-ohrs/Cargo.lock index 0c502ba2..704a568b 100644 --- a/easytier-contrib/easytier-ohrs/Cargo.lock +++ b/easytier-contrib/easytier-ohrs/Cargo.lock @@ -156,6 +156,17 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-recursion" version = "1.1.1" @@ -655,6 +666,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -1181,7 +1201,6 @@ dependencies = [ "derivative", "derive_builder", "derive_more", - "easytier-rpc-build", "encoding", "flume", "forwarded-header-value", @@ -1205,6 +1224,7 @@ dependencies = [ "itertools 0.14.0", "kcp-sys", "machine-uid", + "moka", "multimap", "natpmp", "netlink-packet-core", @@ -1222,6 +1242,7 @@ dependencies = [ "pin-project-lite", "pnet", "prefix-trie", + "proc-macro2", "prost", "prost-build", "prost-reflect", @@ -1231,6 +1252,7 @@ dependencies = [ "prost-wkt-types", "quinn", "quinn-plaintext", + "quote", "rand 0.8.5", "rcgen", "regex", @@ -1263,7 +1285,6 @@ dependencies = [ "tokio-util", "tokio-websockets", "toml", - "tonic-build", "tracing", "tracing-subscriber", "tun-easytier", @@ -1311,14 +1332,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "easytier-rpc-build" -version = "0.1.0" -dependencies = [ - "heck 0.5.0", - "prost-build", -] - [[package]] name = "either" version = "1.15.0" @@ -1469,6 +1482,27 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -2817,9 +2851,12 @@ version = "0.12.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" dependencies = [ + "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", + "event-listener", + "futures-util", "loom", "parking_lot", "portable-atomic", @@ -3236,6 +3273,12 @@ dependencies = [ "unicode-width 0.1.11", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.4" @@ -4991,20 +5034,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" -[[package]] -name = "tonic-build" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" -dependencies = [ - "prettyplease", - "proc-macro2", - "prost-build", - "prost-types", - "quote", - "syn 2.0.106", -] - [[package]] name = "tower" version = "0.5.2" diff --git a/easytier-contrib/easytier-ohrs/src/config/storage/config_meta.rs b/easytier-contrib/easytier-ohrs/src/config/storage/config_meta.rs index a9a920c1..53b3ecaf 100644 --- a/easytier-contrib/easytier-ohrs/src/config/storage/config_meta.rs +++ b/easytier-contrib/easytier-ohrs/src/config/storage/config_meta.rs @@ -1,7 +1,7 @@ use crate::config::types::stored_config::{StoredConfigList, StoredConfigMeta}; use ohos_hilog_binding::{hilog_debug, hilog_error}; use rusqlite::{Connection, OptionalExtension, params}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::Mutex; use std::time::{SystemTime, UNIX_EPOCH}; @@ -101,6 +101,110 @@ fn load_meta_record(conn: &Connection, config_id: &str) -> Option bool { + let has_stored_configs = conn + .query_row( + "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'stored_configs'", + [], + |row| row.get::<_, i64>(0), + ) + .optional() + .ok() + .flatten() + .is_some(); + let has_stored_fields = conn + .query_row( + "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'stored_config_fields'", + [], + |row| row.get::<_, i64>(0), + ) + .optional() + .ok() + .flatten() + .is_some(); + has_stored_configs && has_stored_fields +} + +fn copy_snapshot_tables(src: &Connection, dst: &mut Connection) -> rusqlite::Result<()> { + let mut meta_rows = Vec::::new(); + { + let mut stmt = src.prepare( + "SELECT config_id, display_name, created_at, updated_at, favorite, temporary + FROM stored_configs", + )?; + let rows = stmt.query_map([], row_to_meta)?; + for row in rows { + meta_rows.push(row?); + } + } + + let mut field_rows = Vec::<(String, String, String, String)>::new(); + { + let mut stmt = src.prepare( + "SELECT config_id, field_name, field_json, updated_at + FROM stored_config_fields", + )?; + let rows = stmt.query_map([], |row| { + Ok(( + row.get::<_, String>(0)?, + row.get::<_, String>(1)?, + row.get::<_, String>(2)?, + row.get::<_, String>(3)?, + )) + })?; + for row in rows { + field_rows.push(row?); + } + } + + let tx = dst.unchecked_transaction()?; + tx.execute("DELETE FROM stored_config_fields", [])?; + tx.execute("DELETE FROM stored_configs", [])?; + + for row in meta_rows { + tx.execute( + "INSERT INTO stored_configs ( + config_id, display_name, created_at, updated_at, favorite, temporary + ) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", + params![ + row.config_id, + row.display_name, + row.created_at, + row.updated_at, + if row.favorite { 1 } else { 0 }, + if row.temporary { 1 } else { 0 } + ], + )?; + } + + for (config_id, field_name, field_json, updated_at) in field_rows { + tx.execute( + "INSERT INTO stored_config_fields (config_id, field_name, field_json, updated_at) + VALUES (?1, ?2, ?3, ?4)", + params![config_id, field_name, field_json, updated_at], + )?; + } + + tx.commit() +} + +fn ensure_parent_dir(path: &Path) -> bool { + match path.parent() { + Some(parent) => match std::fs::create_dir_all(parent) { + Ok(_) => true, + Err(e) => { + hilog_error!( + "[Rust] failed to create snapshot parent {}: {}", + parent.display(), + e + ); + false + } + }, + None => true, + } +} + fn to_meta(record: StoredConfigMetaRecord) -> StoredConfigMeta { StoredConfigMeta { config_id: record.config_id, @@ -142,6 +246,79 @@ pub fn init_config_meta_store(root_dir: String) -> bool { true } +pub fn export_config_store_snapshot(target_path: String) -> bool { + let target = PathBuf::from(target_path); + if !ensure_parent_dir(&target) { + return false; + } + let Some(src) = open_db() else { + return false; + }; + let mut dst = match Connection::open(&target) { + Ok(conn) => conn, + Err(e) => { + hilog_error!( + "[Rust] failed to open snapshot target {}: {}", + target.display(), + e + ); + return false; + } + }; + if let Err(e) = init_schema(&dst) { + hilog_error!( + "[Rust] failed to init snapshot schema {}: {}", + target.display(), + e + ); + return false; + } + match copy_snapshot_tables(&src, &mut dst) { + Ok(_) => true, + Err(e) => { + hilog_error!( + "[Rust] failed to export snapshot {}: {}", + target.display(), + e + ); + false + } + } +} + +pub fn import_config_store_snapshot(source_path: String) -> bool { + let source = PathBuf::from(source_path); + let src = match Connection::open(&source) { + Ok(conn) => conn, + Err(e) => { + hilog_error!( + "[Rust] failed to open snapshot source {}: {}", + source.display(), + e + ); + return false; + } + }; + if !validate_snapshot_schema(&src) { + hilog_error!("[Rust] invalid snapshot schema {}", source.display()); + return false; + } + let Some(mut dst) = open_db() else { + return false; + }; + match copy_snapshot_tables(&src, &mut dst) { + Ok(_) => true, + Err(e) => { + hilog_error!( + "[Rust] failed to import snapshot {}: {}", + source.display(), + e + ); + false + } + } +} + pub fn list_config_meta_entries() -> StoredConfigList { let Some(conn) = open_db() else { return StoredConfigList { configs: vec![] }; diff --git a/easytier-contrib/easytier-ohrs/src/exports/config_api.rs b/easytier-contrib/easytier-ohrs/src/exports/config_api.rs index 7bcf413c..ba8bc02e 100644 --- a/easytier-contrib/easytier-ohrs/src/exports/config_api.rs +++ b/easytier-contrib/easytier-ohrs/src/exports/config_api.rs @@ -44,3 +44,11 @@ pub(crate) fn import_toml(toml_text: String, display_name: Option) -> Op pub(crate) fn export_toml(config_id: String) -> Option { config::repository::export_config_toml(&config_id).map(|ret| ret.toml_text) } + +pub(crate) fn export_config_store_snapshot(target_path: String) -> bool { + config::storage::config_meta::export_config_store_snapshot(target_path) +} + +pub(crate) fn import_config_store_snapshot(source_path: String) -> bool { + config::storage::config_meta::import_config_store_snapshot(source_path) +} diff --git a/easytier-contrib/easytier-ohrs/src/lib.rs b/easytier-contrib/easytier-ohrs/src/lib.rs index 48a79968..a8634f0f 100644 --- a/easytier-contrib/easytier-ohrs/src/lib.rs +++ b/easytier-contrib/easytier-ohrs/src/lib.rs @@ -363,6 +363,16 @@ pub fn export_toml(config_id: String) -> Option { exports::config_api::export_toml(config_id) } +#[napi] +pub fn export_config_store_snapshot(target_path: String) -> bool { + exports::config_api::export_config_store_snapshot(target_path) +} + +#[napi] +pub fn import_config_store_snapshot(source_path: String) -> bool { + exports::config_api::import_config_store_snapshot(source_path) +} + #[napi] pub fn start_kernel(config_id: String) -> bool { exports::runtime_api::start_kernel(config_id, start_kernel_with_config_id)