feat(control-plane): add libSQL Turso backend
This commit is contained in:
@@ -9,9 +9,13 @@ use axum::middleware::Next;
|
||||
use axum::response::Response;
|
||||
use axum::Router;
|
||||
use control_plane::{ControlPlaneStore, SqliteControlPlaneStore};
|
||||
#[cfg(not(test))]
|
||||
use control_plane::{TursoControlPlaneConfig, TursoControlPlaneMode, TursoControlPlaneStore};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
#[cfg(not(test))]
|
||||
use std::time::Duration;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::{error, warn};
|
||||
|
||||
@@ -130,6 +134,21 @@ fn read_env_or_dotenv(key: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
fn parse_turso_sync_interval_ms() -> Option<Duration> {
|
||||
let value = env::var("MNOTE_TURSO_SYNC_INTERVAL_MS")
|
||||
.ok()
|
||||
.map(|v| v.trim().to_string())
|
||||
.filter(|v| !v.is_empty())?;
|
||||
let ms: u64 = value
|
||||
.parse()
|
||||
.expect("MNOTE_TURSO_SYNC_INTERVAL_MS 必须为正整数(毫秒),例如 5000");
|
||||
if ms == 0 {
|
||||
panic!("MNOTE_TURSO_SYNC_INTERVAL_MS 必须为正整数,当前值: {ms}");
|
||||
}
|
||||
Some(Duration::from_millis(ms))
|
||||
}
|
||||
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -141,7 +160,7 @@ pub struct AppState {
|
||||
pub stream_delta_tx: broadcast::Sender<serde_json::Value>,
|
||||
pub acp_runtime: Arc<AcpRuntimeManager>,
|
||||
pub buffer_store: BufferStore,
|
||||
control_plane: Arc<SqliteControlPlaneStore>,
|
||||
control_plane: Arc<dyn ControlPlaneStore>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -151,7 +170,7 @@ impl AppState {
|
||||
let actor = EditorRuntimeActor::new();
|
||||
actor.set_block_delta_tx(block_delta_tx.clone());
|
||||
let buffer_store = BufferStore::new();
|
||||
let control_plane = Arc::new(open_control_plane_store());
|
||||
let control_plane = open_control_plane_store();
|
||||
Self {
|
||||
config: Arc::new(config),
|
||||
local_folder_watcher_registry: LocalFolderWatcherRegistry::new(
|
||||
@@ -181,12 +200,33 @@ impl AppState {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn open_control_plane_store() -> SqliteControlPlaneStore {
|
||||
SqliteControlPlaneStore::in_memory().expect("初始化测试 SQLite 控制面")
|
||||
pub(crate) fn open_control_plane_store() -> Arc<dyn ControlPlaneStore> {
|
||||
Arc::new(SqliteControlPlaneStore::in_memory().expect("初始化测试 SQLite 控制面"))
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub(crate) fn open_control_plane_store() -> SqliteControlPlaneStore {
|
||||
pub(crate) fn open_control_plane_store() -> Arc<dyn ControlPlaneStore> {
|
||||
let backend = env::var("MNOTE_CONTROL_PLANE_BACKEND")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_else(|| "sqlite".to_string());
|
||||
match backend.as_str() {
|
||||
"sqlite" => open_sqlite_control_plane_store(),
|
||||
"libsql-local" | "turso-local" | "turso" => open_turso_local_control_plane_store(),
|
||||
"turso-remote" => open_turso_remote_control_plane_store(),
|
||||
"turso-local-replica" | "turso-remote-replica" => {
|
||||
open_turso_remote_replica_control_plane_store()
|
||||
}
|
||||
"turso-synced" => open_turso_synced_control_plane_store(),
|
||||
other => panic!(
|
||||
"不支持的控制面后端 {other},支持 sqlite/libsql-local/turso-remote/turso-local-replica/turso-synced"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
fn open_sqlite_control_plane_store() -> Arc<dyn ControlPlaneStore> {
|
||||
let db_path = env::var("MNOTE_CONTROL_PLANE_DB_PATH")
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
@@ -194,7 +234,114 @@ pub(crate) fn open_control_plane_store() -> SqliteControlPlaneStore {
|
||||
if let Some(parent) = std::path::Path::new(&db_path).parent() {
|
||||
fs::create_dir_all(parent).expect("创建 SQLite 控制面目录");
|
||||
}
|
||||
SqliteControlPlaneStore::open(&db_path).expect("初始化 SQLite 控制面")
|
||||
Arc::new(SqliteControlPlaneStore::open(&db_path).expect("初始化 SQLite 控制面"))
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
fn open_turso_local_control_plane_store() -> Arc<dyn ControlPlaneStore> {
|
||||
let db_path = env::var("MNOTE_TURSO_LOCAL_PATH")
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.or_else(|| {
|
||||
env::var("MNOTE_CONTROL_PLANE_DB_PATH")
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
"/mnt/Data1T/Mnote_data/control-plane/control-plane-libsql.db".to_string()
|
||||
});
|
||||
if let Some(parent) = std::path::Path::new(&db_path).parent() {
|
||||
fs::create_dir_all(parent).expect("创建 libSQL local 控制面目录");
|
||||
}
|
||||
Arc::new(TursoControlPlaneStore::open_local(&db_path).expect("初始化 libSQL local 控制面"))
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
fn open_turso_remote_control_plane_store() -> Arc<dyn ControlPlaneStore> {
|
||||
let url = env::var("MNOTE_TURSO_DATABASE_URL")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.expect("MNOTE_CONTROL_PLANE_BACKEND=turso-remote 需要 MNOTE_TURSO_DATABASE_URL");
|
||||
let token = env::var("MNOTE_TURSO_AUTH_TOKEN")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.expect("MNOTE_CONTROL_PLANE_BACKEND=turso-remote 需要 MNOTE_TURSO_AUTH_TOKEN");
|
||||
Arc::new(TursoControlPlaneStore::open_remote(url, token).expect("初始化 Turso remote 控制面"))
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
fn open_turso_remote_replica_control_plane_store() -> Arc<dyn ControlPlaneStore> {
|
||||
let replica_path = env::var("MNOTE_TURSO_LOCAL_REPLICA_PATH")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_else(|| {
|
||||
"/mnt/Data1T/Mnote_data/control-plane/control-plane-replica.db".to_string()
|
||||
});
|
||||
if let Some(parent) = std::path::Path::new(&replica_path).parent() {
|
||||
fs::create_dir_all(parent).expect("创建 Turso local replica 控制面目录");
|
||||
}
|
||||
let url = env::var("MNOTE_TURSO_DATABASE_URL")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.expect("MNOTE_CONTROL_PLANE_BACKEND=turso-local-replica 需要 MNOTE_TURSO_DATABASE_URL");
|
||||
let token = env::var("MNOTE_TURSO_AUTH_TOKEN")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.expect("MNOTE_CONTROL_PLANE_BACKEND=turso-local-replica 需要 MNOTE_TURSO_AUTH_TOKEN");
|
||||
Arc::new(
|
||||
TursoControlPlaneStore::open_remote_replica(
|
||||
replica_path,
|
||||
url,
|
||||
token,
|
||||
parse_turso_sync_interval_ms(),
|
||||
)
|
||||
.expect("初始化 Turso local replica 控制面"),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
fn open_turso_synced_control_plane_store() -> Arc<dyn ControlPlaneStore> {
|
||||
let local_path = env::var("MNOTE_TURSO_SYNCED_PATH")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.or_else(|| {
|
||||
env::var("MNOTE_TURSO_LOCAL_REPLICA_PATH")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
"/mnt/Data1T/Mnote_data/control-plane/control-plane-synced.db".to_string()
|
||||
});
|
||||
if let Some(parent) = std::path::Path::new(&local_path).parent() {
|
||||
fs::create_dir_all(parent).expect("创建 Turso synced 控制面目录");
|
||||
}
|
||||
let url = env::var("MNOTE_TURSO_DATABASE_URL")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.expect("MNOTE_CONTROL_PLANE_BACKEND=turso-synced 需要 MNOTE_TURSO_DATABASE_URL");
|
||||
let token = env::var("MNOTE_TURSO_AUTH_TOKEN")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.expect("MNOTE_CONTROL_PLANE_BACKEND=turso-synced 需要 MNOTE_TURSO_AUTH_TOKEN");
|
||||
Arc::new(
|
||||
TursoControlPlaneStore::open_with_config(TursoControlPlaneConfig {
|
||||
mode: TursoControlPlaneMode::Synced,
|
||||
path: Some(local_path.into()),
|
||||
remote_url: Some(url),
|
||||
auth_token: Some(token),
|
||||
sync_interval: parse_turso_sync_interval_ms(),
|
||||
})
|
||||
.expect("初始化 Turso synced 控制面"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_app(state: AppState) -> Router {
|
||||
|
||||
Reference in New Issue
Block a user