mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-30 07:37:51 +02:00
chore: version bump
This commit is contained in:
@@ -520,6 +520,7 @@ mod tests {
|
||||
release_type: "stable".to_string(),
|
||||
camoufox_config: None,
|
||||
group_id: None,
|
||||
tags: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,12 +140,27 @@ impl BrowserRunner {
|
||||
|
||||
pub fn save_profile(&self, profile: &BrowserProfile) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager.save_profile(profile)
|
||||
let result = profile_manager.save_profile(profile);
|
||||
// Update tag suggestions after any save
|
||||
let _ = crate::tag_manager::TAG_MANAGER
|
||||
.lock()
|
||||
.map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
pub fn list_profiles(&self) -> Result<Vec<BrowserProfile>, Box<dyn std::error::Error>> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager.list_profiles()
|
||||
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
|
||||
}
|
||||
|
||||
pub async fn launch_browser(
|
||||
@@ -249,8 +264,14 @@ impl BrowserRunner {
|
||||
println!("Updated proxy PID mapping from temp (0) to actual PID: {process_id}");
|
||||
}
|
||||
|
||||
// Save the updated profile
|
||||
self.save_process_info(&updated_profile)?;
|
||||
// Save the updated profile
|
||||
self.save_process_info(&updated_profile)?;
|
||||
// Ensure tag suggestions include any tags from this profile
|
||||
let _ = crate::tag_manager::TAG_MANAGER
|
||||
.lock()
|
||||
.map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
});
|
||||
println!(
|
||||
"Updated profile with process info: {}",
|
||||
updated_profile.name
|
||||
@@ -450,6 +471,11 @@ impl BrowserRunner {
|
||||
updated_profile.last_launch = Some(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs());
|
||||
|
||||
self.save_process_info(&updated_profile)?;
|
||||
let _ = crate::tag_manager::TAG_MANAGER
|
||||
.lock()
|
||||
.map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
});
|
||||
|
||||
// Apply proxy settings if needed (for Firefox-based browsers)
|
||||
if profile.proxy_id.is_some()
|
||||
@@ -815,6 +841,13 @@ impl BrowserRunner {
|
||||
println!("Warning: Failed to cleanup unused binaries: {e}");
|
||||
}
|
||||
|
||||
// Rebuild tags after deletion
|
||||
let _ = crate::tag_manager::TAG_MANAGER
|
||||
.lock()
|
||||
.map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1442,6 +1475,11 @@ impl BrowserRunner {
|
||||
|
||||
files_exist
|
||||
}
|
||||
|
||||
pub fn get_all_tags(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
let tag_manager = crate::tag_manager::TAG_MANAGER.lock().unwrap();
|
||||
tag_manager.get_all_tags()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -1603,6 +1641,14 @@ pub async fn update_profile_proxy(
|
||||
.map_err(|e| format!("Failed to update profile: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn update_profile_tags(profile_name: String, tags: Vec<String>) -> Result<BrowserProfile, String> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager
|
||||
.update_profile_tags(&profile_name, tags)
|
||||
.map_err(|e| format!("Failed to update profile tags: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_browser_status(
|
||||
app_handle: tauri::AppHandle,
|
||||
@@ -1738,6 +1784,14 @@ pub fn is_browser_downloaded(browser_str: String, version: String) -> bool {
|
||||
browser_runner.is_browser_downloaded(&browser_str, &version)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_all_tags() -> Result<Vec<String>, String> {
|
||||
let browser_runner = BrowserRunner::instance();
|
||||
browser_runner
|
||||
.get_all_tags()
|
||||
.map_err(|e| format!("Failed to get tags: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn check_browser_exists(browser_str: String, version: String) -> bool {
|
||||
// This is an alias for is_browser_downloaded to provide clearer semantics for auto-updates
|
||||
|
||||
@@ -400,6 +400,7 @@ mod tests {
|
||||
release_type: "stable".to_string(),
|
||||
camoufox_config: None,
|
||||
group_id: Some(group1.id.clone()),
|
||||
tags: Vec::new(),
|
||||
},
|
||||
crate::profile::BrowserProfile {
|
||||
id: uuid::Uuid::new_v4(),
|
||||
@@ -412,6 +413,7 @@ mod tests {
|
||||
release_type: "stable".to_string(),
|
||||
camoufox_config: None,
|
||||
group_id: Some(group1.id.clone()),
|
||||
tags: Vec::new(),
|
||||
},
|
||||
crate::profile::BrowserProfile {
|
||||
id: uuid::Uuid::new_v4(),
|
||||
@@ -424,6 +426,7 @@ mod tests {
|
||||
release_type: "stable".to_string(),
|
||||
camoufox_config: None,
|
||||
group_id: None, // Default group
|
||||
tags: Vec::new(),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ mod proxy_manager;
|
||||
mod settings_manager;
|
||||
// mod theme_detector; // removed: theme detection handled in webview via CSS prefers-color-scheme
|
||||
mod version_updater;
|
||||
mod tag_manager;
|
||||
|
||||
extern crate lazy_static;
|
||||
|
||||
@@ -68,6 +69,7 @@ use group_manager::{
|
||||
assign_profiles_to_group, create_profile_group, delete_profile_group, delete_selected_profiles,
|
||||
get_groups_with_profile_counts, get_profile_groups, update_profile_group,
|
||||
};
|
||||
use tag_manager::TAG_MANAGER;
|
||||
|
||||
use geoip_downloader::GeoIPDownloader;
|
||||
|
||||
@@ -506,8 +508,10 @@ pub fn run() {
|
||||
fetch_browser_versions_cached_first,
|
||||
fetch_browser_versions_with_count_cached_first,
|
||||
get_downloaded_browser_versions,
|
||||
get_all_tags,
|
||||
get_browser_release_types,
|
||||
update_profile_proxy,
|
||||
update_profile_tags,
|
||||
check_browser_status,
|
||||
kill_browser_profile,
|
||||
rename_profile,
|
||||
|
||||
@@ -151,6 +151,7 @@ impl ProfileManager {
|
||||
release_type: release_type.to_string(),
|
||||
camoufox_config: None,
|
||||
group_id: group_id.clone(),
|
||||
tags: Vec::new(),
|
||||
};
|
||||
|
||||
match camoufox_launcher
|
||||
@@ -191,6 +192,7 @@ impl ProfileManager {
|
||||
release_type: release_type.to_string(),
|
||||
camoufox_config: final_camoufox_config,
|
||||
group_id: group_id.clone(),
|
||||
tags: Vec::new(),
|
||||
};
|
||||
|
||||
// Save profile info
|
||||
@@ -284,6 +286,13 @@ impl ProfileManager {
|
||||
// Save profile with new name
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
// Keep tag suggestions up to date after name change (rebuild from all profiles)
|
||||
let _ = crate::tag_manager::TAG_MANAGER
|
||||
.lock()
|
||||
.map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
});
|
||||
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
@@ -321,6 +330,13 @@ impl ProfileManager {
|
||||
|
||||
println!("Profile '{profile_name}' deleted successfully");
|
||||
|
||||
// Rebuild tag suggestions after deletion
|
||||
let _ = crate::tag_manager::TAG_MANAGER
|
||||
.lock()
|
||||
.map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -395,9 +411,44 @@ impl ProfileManager {
|
||||
self.save_profile(&profile)?;
|
||||
}
|
||||
|
||||
// Rebuild tag suggestions after group changes just in case
|
||||
let _ = crate::tag_manager::TAG_MANAGER
|
||||
.lock()
|
||||
.map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_profile_tags(
|
||||
&self,
|
||||
profile_name: &str,
|
||||
tags: Vec<String>,
|
||||
) -> Result<BrowserProfile, Box<dyn std::error::Error>> {
|
||||
// Find the profile by name
|
||||
let profiles = self.list_profiles()?;
|
||||
let mut profile = profiles
|
||||
.into_iter()
|
||||
.find(|p| p.name == profile_name)
|
||||
.ok_or_else(|| format!("Profile {profile_name} not found"))?;
|
||||
|
||||
// Update tags as-is; preserve characters and order given by caller
|
||||
profile.tags = tags;
|
||||
|
||||
// Save profile
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
// Update global tag suggestions from all profiles
|
||||
let _ = crate::tag_manager::TAG_MANAGER
|
||||
.lock()
|
||||
.map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
});
|
||||
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
pub fn delete_multiple_profiles(
|
||||
&self,
|
||||
profile_names: Vec<String>,
|
||||
|
||||
@@ -20,6 +20,8 @@ pub struct BrowserProfile {
|
||||
pub camoufox_config: Option<CamoufoxConfig>, // Camoufox configuration
|
||||
#[serde(default)]
|
||||
pub group_id: Option<String>, // Reference to profile group
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>, // Free-form tags
|
||||
}
|
||||
|
||||
pub fn default_release_type() -> String {
|
||||
|
||||
@@ -555,6 +555,7 @@ impl ProfileImporter {
|
||||
release_type: "stable".to_string(),
|
||||
camoufox_config: None,
|
||||
group_id: None,
|
||||
tags: Vec::new(),
|
||||
};
|
||||
|
||||
// Save the profile metadata
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
use crate::profile::BrowserProfile;
|
||||
use directories::BaseDirs;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
struct TagsData {
|
||||
tags: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct TagManager {
|
||||
base_dirs: BaseDirs,
|
||||
data_dir_override: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl TagManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base_dirs: BaseDirs::new().expect("Failed to get base directories"),
|
||||
data_dir_override: std::env::var("DONUTBROWSER_DATA_DIR").ok().map(PathBuf::from),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn with_data_dir_override(dir: &Path) -> Self {
|
||||
Self {
|
||||
base_dirs: BaseDirs::new().expect("Failed to get base directories"),
|
||||
data_dir_override: Some(dir.to_path_buf()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tags_file_path(&self) -> PathBuf {
|
||||
if let Some(dir) = &self.data_dir_override {
|
||||
let mut override_path = dir.clone();
|
||||
let _ = fs::create_dir_all(&override_path);
|
||||
override_path.push("tags.json");
|
||||
return override_path;
|
||||
}
|
||||
|
||||
let mut path = self.base_dirs.data_local_dir().to_path_buf();
|
||||
path.push(if cfg!(debug_assertions) { "DonutBrowserDev" } else { "DonutBrowser" });
|
||||
path.push("data");
|
||||
path.push("tags.json");
|
||||
path
|
||||
}
|
||||
|
||||
fn load_tags_data(&self) -> Result<TagsData, Box<dyn std::error::Error>> {
|
||||
let file_path = self.get_tags_file_path();
|
||||
if !file_path.exists() {
|
||||
return Ok(TagsData::default());
|
||||
}
|
||||
let content = fs::read_to_string(file_path)?;
|
||||
let data: TagsData = serde_json::from_str(&content)?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn save_tags_data(&self, data: &TagsData) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let file_path = self.get_tags_file_path();
|
||||
if let Some(parent) = file_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let json = serde_json::to_string_pretty(data)?;
|
||||
fs::write(file_path, json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_all_tags(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
let mut all = self.load_tags_data()?.tags;
|
||||
// Ensure deterministic order
|
||||
all.sort();
|
||||
all.dedup();
|
||||
Ok(all)
|
||||
}
|
||||
|
||||
pub fn suggest_tags(&self, query: Option<&str>) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
let all = self.get_all_tags()?;
|
||||
if let Some(q) = query {
|
||||
let q_lower = q.to_lowercase();
|
||||
Ok(
|
||||
all
|
||||
.into_iter()
|
||||
.filter(|t| t.to_lowercase().contains(&q_lower))
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
Ok(all)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rebuild_from_profiles(
|
||||
&self,
|
||||
profiles: &[BrowserProfile],
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
// Build a set of all tags currently used by any profile
|
||||
let mut set: BTreeSet<String> = BTreeSet::new();
|
||||
for profile in profiles {
|
||||
for tag in &profile.tags {
|
||||
// Store exactly as provided (no normalization) to preserve characters
|
||||
set.insert(tag.clone());
|
||||
}
|
||||
}
|
||||
let combined: Vec<String> = set.into_iter().collect();
|
||||
self.save_tags_data(&TagsData { tags: combined.clone() })?;
|
||||
Ok(combined)
|
||||
}
|
||||
|
||||
pub fn add_tags(&self, tags: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut data = self.load_tags_data()?;
|
||||
data.tags.extend(tags.iter().cloned());
|
||||
data.tags.sort();
|
||||
data.tags.dedup();
|
||||
self.save_tags_data(&data)
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref TAG_MANAGER: std::sync::Mutex<TagManager> = std::sync::Mutex::new(TagManager::new());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user