mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-07 15:33:57 +02:00
feat: block launching profiles for incompatible systems
This commit is contained in:
@@ -520,6 +520,7 @@ mod tests {
|
||||
note: None,
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::browser::{create_browser, BrowserType, ProxySettings};
|
||||
use crate::camoufox_manager::CamoufoxConfig;
|
||||
use crate::downloaded_browsers_registry::DownloadedBrowsersRegistry;
|
||||
use crate::events;
|
||||
use crate::profile::types::BrowserProfile;
|
||||
use crate::profile::types::{get_host_os, BrowserProfile};
|
||||
use crate::proxy_manager::PROXY_MANAGER;
|
||||
use crate::wayfern_manager::WayfernConfig;
|
||||
use directories::BaseDirs;
|
||||
@@ -173,6 +173,7 @@ impl ProfileManager {
|
||||
note: None,
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: None,
|
||||
};
|
||||
|
||||
match self
|
||||
@@ -286,6 +287,7 @@ impl ProfileManager {
|
||||
note: None,
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: None,
|
||||
};
|
||||
|
||||
match self
|
||||
@@ -331,6 +333,7 @@ impl ProfileManager {
|
||||
note: None,
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: Some(get_host_os()),
|
||||
};
|
||||
|
||||
// Save profile info
|
||||
@@ -466,8 +469,8 @@ impl ProfileManager {
|
||||
.find(|p| p.id == profile_uuid)
|
||||
.ok_or_else(|| format!("Profile with ID '{profile_id}' not found"))?;
|
||||
|
||||
// Check if browser is running
|
||||
if profile.process_id.is_some() {
|
||||
// Check if browser is running (cross-OS profiles can't be running locally)
|
||||
if profile.process_id.is_some() && !profile.is_cross_os() {
|
||||
return Err(
|
||||
"Cannot delete profile while browser is running. Please stop the browser first.".into(),
|
||||
);
|
||||
@@ -733,8 +736,8 @@ impl ProfileManager {
|
||||
.find(|p| p.id == profile_uuid)
|
||||
.ok_or_else(|| format!("Profile with ID '{profile_id}' not found"))?;
|
||||
|
||||
// Check if browser is running
|
||||
if profile.process_id.is_some() {
|
||||
// Check if browser is running (cross-OS profiles can't be running locally)
|
||||
if profile.process_id.is_some() && !profile.is_cross_os() {
|
||||
return Err(
|
||||
format!(
|
||||
"Cannot delete profile '{}' while browser is running. Please stop the browser first.",
|
||||
@@ -847,6 +850,7 @@ impl ProfileManager {
|
||||
note: source.note,
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: Some(get_host_os()),
|
||||
};
|
||||
|
||||
self.save_profile(&new_profile)?;
|
||||
|
||||
@@ -41,15 +41,36 @@ pub struct BrowserProfile {
|
||||
pub sync_enabled: bool, // Whether sync is enabled for this profile
|
||||
#[serde(default)]
|
||||
pub last_sync: Option<u64>, // Timestamp of last successful sync (epoch seconds)
|
||||
#[serde(default)]
|
||||
pub host_os: Option<String>, // OS where profile was created ("macos", "windows", "linux")
|
||||
}
|
||||
|
||||
pub fn default_release_type() -> String {
|
||||
"stable".to_string()
|
||||
}
|
||||
|
||||
pub fn get_host_os() -> String {
|
||||
if cfg!(target_os = "macos") {
|
||||
"macos".to_string()
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"windows".to_string()
|
||||
} else {
|
||||
"linux".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserProfile {
|
||||
/// Get the path to the profile data directory (profiles/{uuid}/profile)
|
||||
pub fn get_profile_data_path(&self, profiles_dir: &Path) -> PathBuf {
|
||||
profiles_dir.join(self.id.to_string()).join("profile")
|
||||
}
|
||||
|
||||
/// Returns true when the profile was created on a different OS than the current host.
|
||||
/// Profiles without an `os` field (backward compat) are treated as native.
|
||||
pub fn is_cross_os(&self) -> bool {
|
||||
match &self.host_os {
|
||||
Some(host_os) => host_os != &get_host_os(),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,6 +555,7 @@ impl ProfileImporter {
|
||||
note: None,
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: Some(crate::profile::types::get_host_os()),
|
||||
};
|
||||
|
||||
// Save the profile metadata
|
||||
|
||||
@@ -59,6 +59,15 @@ impl SyncEngine {
|
||||
app_handle: &tauri::AppHandle,
|
||||
profile: &BrowserProfile,
|
||||
) -> SyncResult<()> {
|
||||
if profile.is_cross_os() {
|
||||
log::info!(
|
||||
"Skipping file sync for cross-OS profile: {} ({})",
|
||||
profile.name,
|
||||
profile.id
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let profile_manager = ProfileManager::instance();
|
||||
let profiles_dir = profile_manager.get_profiles_dir();
|
||||
let profile_dir = profiles_dir.join(profile.id.to_string());
|
||||
@@ -832,6 +841,49 @@ impl SyncEngine {
|
||||
let mut profile: BrowserProfile = serde_json::from_slice(&metadata_data)
|
||||
.map_err(|e| SyncError::SerializationError(format!("Failed to parse metadata: {e}")))?;
|
||||
|
||||
// Cross-OS profile: save metadata only, skip manifest + file downloads
|
||||
if profile.is_cross_os() {
|
||||
log::info!(
|
||||
"Profile {} is cross-OS (host_os={:?}), downloading metadata only",
|
||||
profile_id,
|
||||
profile.host_os
|
||||
);
|
||||
|
||||
fs::create_dir_all(&profile_dir).map_err(|e| {
|
||||
SyncError::IoError(format!(
|
||||
"Failed to create profile directory {}: {e}",
|
||||
profile_dir.display()
|
||||
))
|
||||
})?;
|
||||
|
||||
profile.sync_enabled = true;
|
||||
profile.last_sync = Some(
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
);
|
||||
|
||||
profile_manager
|
||||
.save_profile(&profile)
|
||||
.map_err(|e| SyncError::IoError(format!("Failed to save cross-OS profile: {e}")))?;
|
||||
|
||||
let _ = events::emit("profiles-changed", ());
|
||||
let _ = events::emit(
|
||||
"profile-sync-status",
|
||||
serde_json::json!({
|
||||
"profile_id": profile_id,
|
||||
"status": "synced"
|
||||
}),
|
||||
);
|
||||
|
||||
log::info!(
|
||||
"Cross-OS profile {} metadata downloaded successfully",
|
||||
profile_id
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// Download manifest
|
||||
let manifest = self.download_manifest(&manifest_key).await?;
|
||||
let Some(manifest) = manifest else {
|
||||
@@ -940,6 +992,57 @@ impl SyncEngine {
|
||||
log::info!("No missing profiles found");
|
||||
}
|
||||
|
||||
// Refresh metadata for local cross-OS profiles (propagate renames, tags, notes from originating device)
|
||||
let profile_manager = ProfileManager::instance();
|
||||
// Collect cross-OS profiles before async operations to avoid holding non-Send Result across await
|
||||
let cross_os_profiles: Vec<(String, bool)> = profile_manager
|
||||
.list_profiles()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter(|p| p.is_cross_os() && p.sync_enabled)
|
||||
.map(|p| (p.id.to_string(), p.sync_enabled))
|
||||
.collect();
|
||||
|
||||
if !cross_os_profiles.is_empty() {
|
||||
for (pid, sync_enabled) in &cross_os_profiles {
|
||||
let metadata_key = format!("profiles/{}/metadata.json", pid);
|
||||
match self.client.stat(&metadata_key).await {
|
||||
Ok(stat) if stat.exists => match self.client.presign_download(&metadata_key).await {
|
||||
Ok(presign) => match self.client.download_bytes(&presign.url).await {
|
||||
Ok(data) => {
|
||||
if let Ok(mut remote_profile) = serde_json::from_slice::<BrowserProfile>(&data) {
|
||||
remote_profile.sync_enabled = *sync_enabled;
|
||||
remote_profile.last_sync = Some(
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
);
|
||||
if let Err(e) = profile_manager.save_profile(&remote_profile) {
|
||||
log::warn!("Failed to refresh cross-OS profile {} metadata: {}", pid, e);
|
||||
} else {
|
||||
log::debug!("Refreshed cross-OS profile {} metadata", pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"Failed to download cross-OS profile {} metadata: {}",
|
||||
pid,
|
||||
e
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to presign cross-OS profile {} metadata: {}", pid, e);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let _ = events::emit("profiles-changed", ());
|
||||
}
|
||||
|
||||
Ok(downloaded)
|
||||
}
|
||||
}
|
||||
@@ -1048,6 +1151,10 @@ pub async fn set_profile_sync_enabled(
|
||||
.find(|p| p.id == profile_uuid)
|
||||
.ok_or_else(|| format!("Profile with ID '{profile_id}' not found"))?;
|
||||
|
||||
if profile.is_cross_os() {
|
||||
return Err("Cannot modify sync settings for a cross-OS profile".to_string());
|
||||
}
|
||||
|
||||
// If enabling, first check that sync settings are configured
|
||||
if enabled {
|
||||
// Cloud auth provides sync settings dynamically — skip local checks
|
||||
|
||||
Reference in New Issue
Block a user