feat(dev): support running alongside installed DBX

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-07-23 17:06:08 +08:00
1 parent 2318c48787
commit eab263d3b4
9 files changed
+197 -18

No files matched your search

+22 -3
View File
@@ -11,6 +11,16 @@ use crate::{
apply_debug_log_level, apply_desktop_settings, hide_main_window_for_close, request_app_close, CloseBehaviorState,
};
const DEVELOPMENT_OPEN_TABS_STATE_KEY: &str = "development_open_tabs";
fn open_tabs_state_key(debug_build: bool) -> &'static str {
if debug_build {
DEVELOPMENT_OPEN_TABS_STATE_KEY
} else {
"open_tabs"
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DriverStoreMigrationResult {
pub driver_store_dir: Option<String>,
@@ -115,12 +125,12 @@ pub async fn save_editor_settings(state: State<'_, Arc<AppState>>, settings: ser
#[tauri::command]
pub async fn load_open_tabs_state(state: State<'_, Arc<AppState>>) -> Result<Option<serde_json::Value>, String> {
state.storage.load_open_tabs_state().await
state.storage.load_open_tabs_state_with_key(open_tabs_state_key(cfg!(debug_assertions))).await
}
#[tauri::command]
pub async fn save_open_tabs_state(state: State<'_, Arc<AppState>>, payload: serde_json::Value) -> Result<(), String> {
state.storage.save_open_tabs_state(&payload).await
state.storage.save_open_tabs_state_with_key(open_tabs_state_key(cfg!(debug_assertions)), &payload).await
}
#[tauri::command]
@@ -481,7 +491,10 @@ fn load_native_debug_logs_from_dir(log_dir: PathBuf) -> Result<String, String> {
#[cfg(test)]
mod tests {
use super::{driver_store_migration_result, resolve_driver_store_dirs_from_settings, DesktopSettings};
use super::{
driver_store_migration_result, open_tabs_state_key, resolve_driver_store_dirs_from_settings, DesktopSettings,
DEVELOPMENT_OPEN_TABS_STATE_KEY,
};
use std::path::PathBuf;
#[test]
@@ -548,6 +561,12 @@ mod tests {
assert_eq!(agents_dir, Some(PathBuf::from(path("D:/develop/DBX/agents"))));
}
#[test]
fn isolates_open_tabs_for_development_builds() {
assert_eq!(open_tabs_state_key(true), DEVELOPMENT_OPEN_TABS_STATE_KEY);
assert_eq!(open_tabs_state_key(false), "open_tabs");
}
fn path(value: &str) -> String {
value.replace('/', std::path::MAIN_SEPARATOR_STR)
}
+72 -7
View File
@@ -1,4 +1,5 @@
use std::sync::Arc;
use std::{net::Ipv4Addr, net::TcpListener};
use axum::extract::ws::{Message, WebSocket};
use axum::extract::{Query, State, WebSocketUpgrade};
@@ -12,6 +13,18 @@ use dbx_core::connection::AppState;
const DEFAULT_PUBSUB_PORT: u16 = 4224;
pub struct PubSubServerPort(Option<u16>);
impl PubSubServerPort {
fn new(port: Option<u16>) -> Self {
Self(port)
}
fn get(&self) -> Result<u16, String> {
self.0.ok_or_else(|| "Redis PubSub server is unavailable".to_string())
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct PubSubWsParams {
@@ -27,8 +40,24 @@ fn pubsub_server_port() -> u16 {
}
#[tauri::command]
pub fn redis_pubsub_server_port() -> u16 {
pubsub_server_port()
pub fn redis_pubsub_server_port(port: tauri::State<'_, PubSubServerPort>) -> Result<u16, String> {
port.get()
}
fn bind_pubsub_listener(preferred_port: u16) -> Result<TcpListener, String> {
let preferred_addr = (Ipv4Addr::LOCALHOST, preferred_port);
match TcpListener::bind(preferred_addr) {
Ok(listener) => Ok(listener),
Err(preferred_error) if preferred_port != 0 => {
log::warn!(
"Failed to bind PubSub server on {preferred_addr:?}: {preferred_error}; using an available port instead"
);
TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).map_err(|fallback_error| {
format!("Failed to bind PubSub server on an available port: {fallback_error}")
})
}
Err(error) => Err(format!("Failed to bind PubSub server on {preferred_addr:?}: {error}")),
}
}
async fn ws_handler(
@@ -144,15 +173,32 @@ async fn handle_command(sink: &mut redis::aio::PubSubSink, text: &str) -> Result
/// Start the embedded web server for PubSub WebSocket support.
/// Runs on a background task using the shared AppState.
pub fn start_pubsub_server(state: Arc<AppState>) {
pub fn start_pubsub_server(state: Arc<AppState>) -> PubSubServerPort {
let router = build_pubsub_router(state);
let listener = match bind_pubsub_listener(pubsub_server_port()) {
Ok(listener) => listener,
Err(error) => {
log::warn!("{error}");
return PubSubServerPort::new(None);
}
};
let addr = match listener.local_addr() {
Ok(addr) => addr,
Err(error) => {
log::warn!("Failed to read PubSub server address: {error}");
return PubSubServerPort::new(None);
}
};
if let Err(error) = listener.set_nonblocking(true) {
log::warn!("Failed to configure PubSub server listener: {error}");
return PubSubServerPort::new(None);
}
tauri::async_runtime::spawn(async move {
let port = pubsub_server_port();
let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port));
let listener = match tokio::net::TcpListener::bind(addr).await {
let listener = match tokio::net::TcpListener::from_std(listener) {
Ok(listener) => listener,
Err(error) => {
log::warn!("Failed to bind PubSub server on {addr}: {error}");
log::warn!("Failed to start PubSub server on {addr}: {error}");
return;
}
};
@@ -161,4 +207,23 @@ pub fn start_pubsub_server(state: Arc<AppState>) {
log::warn!("PubSub server stopped with error: {error}");
}
});
PubSubServerPort::new(Some(addr.port()))
}
#[cfg(test)]
mod tests {
use super::bind_pubsub_listener;
use std::net::{Ipv4Addr, TcpListener};
#[test]
fn falls_back_to_an_available_local_port_when_the_preferred_port_is_in_use() {
let occupied = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
let preferred_port = occupied.local_addr().unwrap().port();
let listener = bind_pubsub_listener(preferred_port).unwrap();
assert_eq!(listener.local_addr().unwrap().ip(), Ipv4Addr::LOCALHOST);
assert_ne!(listener.local_addr().unwrap().port(), preferred_port);
}
}