refactor: reduce table re-renders

This commit is contained in:
zhom
2025-08-17 11:31:24 +04:00
parent 2e6bb2498b
commit f7e116f345
7 changed files with 684 additions and 498 deletions
-8
View File
@@ -470,14 +470,6 @@ pub async fn check_for_browser_updates() -> Result<Vec<UpdateNotification>, Stri
Ok(grouped)
}
#[tauri::command]
pub async fn is_browser_disabled_for_update(browser: String) -> Result<bool, String> {
let updater = AutoUpdater::instance();
updater
.is_browser_disabled(&browser)
.map_err(|e| format!("Failed to check browser status: {e}"))
}
#[tauri::command]
pub async fn dismiss_update_notification(notification_id: String) -> Result<(), String> {
let updater = AutoUpdater::instance();
+178 -20
View File
@@ -2,6 +2,7 @@ use crate::platform_browser;
use crate::profile::{BrowserProfile, ProfileManager};
use crate::proxy_manager::PROXY_MANAGER;
use directories::BaseDirs;
use serde::Serialize;
use std::collections::HashSet;
use std::fs::create_dir_all;
use std::path::{Path, PathBuf};
@@ -151,11 +152,6 @@ impl BrowserRunner {
pub fn list_profiles(&self) -> Result<Vec<BrowserProfile>, Box<dyn std::error::Error>> {
let profile_manager = ProfileManager::instance();
let profiles = profile_manager.list_profiles();
if let Ok(ref ps) = profiles {
let _ = crate::tag_manager::TAG_MANAGER.lock().map(|tm| {
let _ = tm.rebuild_from_profiles(ps);
});
}
profiles
}
@@ -271,11 +267,35 @@ impl BrowserRunner {
updated_profile.name
);
println!(
"Emitting profile events for successful Camoufox launch: {}",
updated_profile.name
);
// Emit profile update event to frontend
if let Err(e) = app_handle.emit("profile-updated", &updated_profile) {
println!("Warning: Failed to emit profile update event: {e}");
}
// Emit minimal running changed event to frontend with a small delay
#[derive(Serialize)]
struct RunningChangedPayload {
id: String,
is_running: bool,
}
let payload = RunningChangedPayload {
id: updated_profile.id.to_string(),
is_running: updated_profile.process_id.is_some(),
};
if let Err(e) = app_handle.emit("profile-running-changed", &payload) {
println!("Warning: Failed to emit profile running changed event: {e}");
} else {
println!("Emitted profile update event for: {}", updated_profile.name);
println!(
"Successfully emitted profile-running-changed event for Camoufox {}: running={}",
updated_profile.name, payload.is_running
);
}
return Ok(updated_profile);
@@ -484,11 +504,36 @@ impl BrowserRunner {
// which is already handled in the profile creation process
}
println!(
"Emitting profile events for successful launch: {}",
updated_profile.name
);
// Emit profile update event to frontend
if let Err(e) = app_handle.emit("profile-updated", &updated_profile) {
println!("Warning: Failed to emit profile update event: {e}");
}
// Emit minimal running changed event to frontend with a small delay to ensure UI consistency
#[derive(Serialize)]
struct RunningChangedPayload {
id: String,
is_running: bool,
}
let payload = RunningChangedPayload {
id: updated_profile.id.to_string(),
is_running: updated_profile.process_id.is_some(),
};
if let Err(e) = app_handle.emit("profile-running-changed", &payload) {
println!("Warning: Failed to emit profile running changed event: {e}");
} else {
println!(
"Successfully emitted profile-running-changed event for {}: running={}",
updated_profile.name, payload.is_running
);
}
Ok(updated_profile)
}
@@ -705,20 +750,27 @@ impl BrowserRunner {
url: Option<String>,
internal_proxy_settings: Option<&ProxySettings>,
) -> Result<BrowserProfile, Box<dyn std::error::Error + Send + Sync>> {
println!("launch_or_open_url called for profile: {}", profile.name);
// Get the most up-to-date profile data
let profiles = self.list_profiles().expect("Failed to list profiles");
let profiles = self.list_profiles()
.map_err(|e| format!("Failed to list profiles in launch_or_open_url: {}", e))?;
let updated_profile = profiles
.into_iter()
.find(|p| p.name == profile.name)
.unwrap_or_else(|| profile.clone());
println!("Checking browser status for profile: {}", updated_profile.name);
// Check if browser is already running
let is_running = self
.check_browser_status(app_handle.clone(), &updated_profile)
.await?;
.await
.map_err(|e| format!("Failed to check browser status: {}", e))?;
// Get the updated profile again after status check (PID might have been updated)
let profiles = self.list_profiles().expect("Failed to list profiles");
let profiles = self.list_profiles()
.map_err(|e| format!("Failed to list profiles after status check: {}", e))?;
let final_profile = profiles
.into_iter()
.find(|p| p.name == profile.name)
@@ -927,11 +979,36 @@ impl BrowserRunner {
.save_process_info(&updated_profile)
.map_err(|e| format!("Failed to update profile: {e}"))?;
println!(
"Emitting profile events for successful Camoufox kill: {}",
updated_profile.name
);
// Emit profile update event to frontend
if let Err(e) = app_handle.emit("profile-updated", &updated_profile) {
println!("Warning: Failed to emit profile update event: {e}");
}
// Emit minimal running changed event to frontend immediately
#[derive(Serialize)]
struct RunningChangedPayload {
id: String,
is_running: bool,
}
let payload = RunningChangedPayload {
id: updated_profile.id.to_string(),
is_running: false, // Explicitly set to false since we just killed it
};
if let Err(e) = app_handle.emit("profile-running-changed", &payload) {
println!("Warning: Failed to emit profile running changed event: {e}");
} else {
println!(
"Successfully emitted profile-running-changed event for Camoufox {}: running={}",
updated_profile.name, payload.is_running
);
}
println!(
"Camoufox process cleanup completed for profile: {}",
profile.name
@@ -1036,6 +1113,36 @@ impl BrowserRunner {
.save_process_info(&updated_profile)
.map_err(|e| format!("Failed to update profile: {e}"))?;
println!(
"Emitting profile events for successful kill: {}",
updated_profile.name
);
// Emit profile update event to frontend
if let Err(e) = app_handle.emit("profile-updated", &updated_profile) {
println!("Warning: Failed to emit profile update event: {e}");
}
// Emit minimal running changed event to frontend immediately
#[derive(Serialize)]
struct RunningChangedPayload {
id: String,
is_running: bool,
}
let payload = RunningChangedPayload {
id: updated_profile.id.to_string(),
is_running: false, // Explicitly set to false since we just killed it
};
if let Err(e) = app_handle.emit("profile-running-changed", &payload) {
println!("Warning: Failed to emit profile running changed event: {e}");
} else {
println!(
"Successfully emitted profile-running-changed event for {}: running={}",
updated_profile.name, payload.is_running
);
}
Ok(())
}
@@ -1515,18 +1622,28 @@ pub async fn launch_browser_profile(
profile: BrowserProfile,
url: Option<String>,
) -> Result<BrowserProfile, String> {
println!("Launch request received for profile: {}", profile.name);
let browser_runner = BrowserRunner::instance();
// Store the internal proxy settings for passing to launch_browser
let mut internal_proxy_settings: Option<ProxySettings> = None;
// Resolve the most up-to-date profile from disk by name to avoid using stale proxy_id/browser state
let profile_for_launch = browser_runner
let profile_for_launch = match browser_runner
.list_profiles()
.map_err(|e| format!("Failed to list profiles: {e}"))?
.into_iter()
.find(|p| p.name == profile.name)
.unwrap_or_else(|| profile.clone());
.map_err(|e| format!("Failed to list profiles: {e}"))
{
Ok(profiles) => profiles
.into_iter()
.find(|p| p.name == profile.name)
.unwrap_or_else(|| profile.clone()),
Err(e) => {
return Err(e);
}
};
println!("Resolved profile for launch: {}", profile_for_launch.name);
// Always start a local proxy before launching (non-Camoufox handled here; Camoufox has its own flow)
if profile.browser != "camoufox" {
@@ -1585,9 +1702,6 @@ pub async fn launch_browser_profile(
.map(|p| format!("{}:{}", p.host, p.port))
.unwrap_or_else(|| "DIRECT".to_string())
);
// Give the proxy a moment to fully start up
tokio::time::sleep(tokio::time::Duration::from_millis(300)).await;
}
Err(e) => {
eprintln!("Failed to start local proxy (will launch without it): {e}");
@@ -1595,11 +1709,27 @@ pub async fn launch_browser_profile(
}
}
println!("Starting browser launch for profile: {}", profile_for_launch.name);
// Launch browser or open URL in existing instance
let updated_profile = browser_runner
.launch_or_open_url(app_handle.clone(), &profile_for_launch, url, internal_proxy_settings.as_ref())
.await
.map_err(|e| {
println!("Browser launch failed for profile: {}, error: {}", profile_for_launch.name, e);
// Emit a failure event to clear loading states in the frontend
#[derive(serde::Serialize)]
struct RunningChangedPayload {
id: String,
is_running: bool,
}
let payload = RunningChangedPayload {
id: profile_for_launch.id.to_string(),
is_running: false,
};
let _ = app_handle.emit("profile-running-changed", &payload);
// Check if this is an architecture compatibility issue
if let Some(io_error) = e.downcast_ref::<std::io::Error>() {
if io_error.kind() == std::io::ErrorKind::Other
@@ -1610,6 +1740,8 @@ pub async fn launch_browser_profile(
format!("Failed to launch browser or open URL: {e}")
})?;
println!("Browser launch completed for profile: {}", updated_profile.name);
// Now update the proxy with the correct PID if we have one
if let Some(actual_pid) = updated_profile.process_id {
// Update the proxy manager with the correct PID (we always started with temp pid 1 for non-Camoufox)
@@ -1797,11 +1929,37 @@ pub async fn kill_browser_profile(
app_handle: tauri::AppHandle,
profile: BrowserProfile,
) -> Result<(), String> {
println!("Kill request received for profile: {}", profile.name);
let browser_runner = BrowserRunner::instance();
browser_runner
.kill_browser_process(app_handle, &profile)
match browser_runner
.kill_browser_process(app_handle.clone(), &profile)
.await
.map_err(|e| format!("Failed to kill browser: {e}"))
{
Ok(()) => {
println!("Successfully killed browser profile: {}", profile.name);
Ok(())
}
Err(e) => {
println!("Failed to kill browser profile {}: {}", profile.name, e);
// Emit a failure event to clear loading states in the frontend
#[derive(serde::Serialize)]
struct RunningChangedPayload {
id: String,
is_running: bool,
}
// On kill failure, we assume the process is still running
let payload = RunningChangedPayload {
id: profile.id.to_string(),
is_running: true,
};
let _ = app_handle.emit("profile-running-changed", &payload);
Err(format!("Failed to kill browser: {e}"))
}
}
}
#[allow(clippy::too_many_arguments)]
+79 -3
View File
@@ -53,7 +53,6 @@ use version_updater::{
use auto_updater::{
check_for_browser_updates, complete_browser_update_with_auto_update, dismiss_update_notification,
is_browser_disabled_for_update,
};
use app_auto_updater::{
@@ -489,6 +488,84 @@ pub fn run() {
}
});
// Periodically broadcast browser running status to the frontend
let app_handle_status = app.handle().clone();
tauri::async_runtime::spawn(async move {
let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(500));
let mut last_running_states: std::collections::HashMap<String, bool> =
std::collections::HashMap::new();
loop {
interval.tick().await;
let runner = crate::browser_runner::BrowserRunner::instance();
// If listing profiles fails, skip this tick
let profiles = match runner.list_profiles() {
Ok(p) => p,
Err(e) => {
println!("Warning: Failed to list profiles in status checker: {}", e);
continue;
}
};
for profile in profiles {
// Check browser status and track changes
match runner
.check_browser_status(app_handle_status.clone(), &profile)
.await
{
Ok(is_running) => {
let profile_id = profile.id.to_string();
let last_state = last_running_states
.get(&profile_id)
.copied()
.unwrap_or(false);
// Only emit event if state actually changed
if last_state != is_running {
println!(
"Status checker detected change for profile {}: {} -> {}",
profile.name, last_state, is_running
);
#[derive(serde::Serialize)]
struct RunningChangedPayload {
id: String,
is_running: bool,
}
let payload = RunningChangedPayload {
id: profile_id.clone(),
is_running,
};
if let Err(e) = app_handle_status.emit("profile-running-changed", &payload) {
println!("Warning: Failed to emit profile running changed event: {e}");
} else {
println!(
"Status checker emitted profile-running-changed event for {}: running={}",
profile.name, is_running
);
}
last_running_states.insert(profile_id, is_running);
} else {
// Update the state even if unchanged to ensure we have it tracked
last_running_states.insert(profile_id, is_running);
}
}
Err(e) => {
println!(
"Warning: Status check failed for profile {}: {}",
profile.name, e
);
continue;
}
}
}
}
});
// Nodecar warm-up is now triggered from the frontend to allow UI blocking overlay
// Start API server if enabled in settings
@@ -574,7 +651,6 @@ pub fn run() {
trigger_manual_version_update,
get_version_update_status,
check_for_browser_updates,
is_browser_disabled_for_update,
dismiss_update_notification,
complete_browser_update_with_auto_update,
check_for_app_updates,
@@ -603,7 +679,7 @@ pub fn run() {
warm_up_nodecar,
start_api_server,
stop_api_server,
get_api_server_status,
get_api_server_status
])
.run(tauri::generate_context!())
.expect("error while running tauri application");