support delete bookmarks
This commit is contained in:
@@ -7,9 +7,11 @@ use std::{
|
||||
use crate::{
|
||||
config_store,
|
||||
models::{
|
||||
BrowserConfigListResponse, CleanupHistoryInput, CleanupHistoryResponse,
|
||||
CleanupHistoryResult, CreateCustomBrowserConfigInput, ExtensionInstallSourceSummary,
|
||||
RemoveExtensionResult, RemoveExtensionsInput, RemoveExtensionsResponse, ScanResponse,
|
||||
BookmarkRemovalRequest, BrowserConfigListResponse, CleanupHistoryInput,
|
||||
CleanupHistoryResponse, CleanupHistoryResult, CreateCustomBrowserConfigInput,
|
||||
ExtensionInstallSourceSummary, RemoveBookmarkResult, RemoveBookmarksInput,
|
||||
RemoveBookmarksResponse, RemoveExtensionResult, RemoveExtensionsInput,
|
||||
RemoveExtensionsResponse, ScanResponse,
|
||||
},
|
||||
scanner,
|
||||
};
|
||||
@@ -124,6 +126,35 @@ pub fn remove_extensions(
|
||||
Ok(RemoveExtensionsResponse { results })
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn remove_bookmarks(
|
||||
app: AppHandle,
|
||||
input: RemoveBookmarksInput,
|
||||
) -> Result<RemoveBookmarksResponse, String> {
|
||||
let config = config_store::find_browser_config(&app, &input.browser_id)?;
|
||||
let user_data_dir = PathBuf::from(&config.user_data_path);
|
||||
|
||||
if !user_data_dir.is_dir() {
|
||||
return Err(format!(
|
||||
"User data directory does not exist: {}",
|
||||
user_data_dir.display()
|
||||
));
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
for removal in input.removals {
|
||||
for profile_id in &removal.profile_ids {
|
||||
results.push(remove_bookmark_from_profile(
|
||||
&user_data_dir.join(profile_id),
|
||||
&removal,
|
||||
profile_id,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RemoveBookmarksResponse { results })
|
||||
}
|
||||
|
||||
fn spawn_browser_process(
|
||||
executable_path: PathBuf,
|
||||
user_data_dir: PathBuf,
|
||||
@@ -298,6 +329,106 @@ fn remove_extension_from_profile(
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_bookmark_from_profile(
|
||||
profile_path: &Path,
|
||||
removal: &BookmarkRemovalRequest,
|
||||
profile_id: &str,
|
||||
) -> RemoveBookmarkResult {
|
||||
if !profile_path.is_dir() {
|
||||
return RemoveBookmarkResult {
|
||||
url: removal.url.clone(),
|
||||
profile_id: profile_id.to_string(),
|
||||
removed_count: 0,
|
||||
removed_files: Vec::new(),
|
||||
skipped_files: Vec::new(),
|
||||
error: Some(format!(
|
||||
"Profile directory does not exist: {}",
|
||||
profile_path.display()
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
let mut removed_files = Vec::new();
|
||||
let mut skipped_files = Vec::new();
|
||||
|
||||
let removed_backup = remove_bookmark_backups(profile_path).map_err(|error| RemoveBookmarkResult {
|
||||
url: removal.url.clone(),
|
||||
profile_id: profile_id.to_string(),
|
||||
removed_count: 0,
|
||||
removed_files: removed_files.clone(),
|
||||
skipped_files: skipped_files.clone(),
|
||||
error: Some(error),
|
||||
});
|
||||
let removed_backup = match removed_backup {
|
||||
Ok(value) => value,
|
||||
Err(result) => return result,
|
||||
};
|
||||
if removed_backup {
|
||||
removed_files.push("Bookmarks.bak".to_string());
|
||||
} else {
|
||||
skipped_files.push("Bookmarks.bak".to_string());
|
||||
}
|
||||
|
||||
let Some(bookmarks_path) = resolve_bookmarks_path(profile_path) else {
|
||||
return RemoveBookmarkResult {
|
||||
url: removal.url.clone(),
|
||||
profile_id: profile_id.to_string(),
|
||||
removed_count: 0,
|
||||
removed_files,
|
||||
skipped_files,
|
||||
error: Some(format!(
|
||||
"Bookmarks file does not exist in {}",
|
||||
profile_path.display()
|
||||
)),
|
||||
};
|
||||
};
|
||||
|
||||
let mut document = match read_json_document(&bookmarks_path) {
|
||||
Ok(document) => document,
|
||||
Err(error) => {
|
||||
return RemoveBookmarkResult {
|
||||
url: removal.url.clone(),
|
||||
profile_id: profile_id.to_string(),
|
||||
removed_count: 0,
|
||||
removed_files,
|
||||
skipped_files,
|
||||
error: Some(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let checksum_removed = document
|
||||
.as_object_mut()
|
||||
.and_then(|object| object.remove("checksum"))
|
||||
.is_some();
|
||||
let removed_count = remove_matching_bookmarks(&mut document, &removal.url);
|
||||
|
||||
if checksum_removed || removed_count > 0 {
|
||||
if let Err(error) = write_json_document(&bookmarks_path, &document) {
|
||||
return RemoveBookmarkResult {
|
||||
url: removal.url.clone(),
|
||||
profile_id: profile_id.to_string(),
|
||||
removed_count: 0,
|
||||
removed_files,
|
||||
skipped_files,
|
||||
error: Some(error),
|
||||
};
|
||||
}
|
||||
removed_files.push("Bookmarks".to_string());
|
||||
} else {
|
||||
skipped_files.push("Bookmarks".to_string());
|
||||
}
|
||||
|
||||
RemoveBookmarkResult {
|
||||
url: removal.url.clone(),
|
||||
profile_id: profile_id.to_string(),
|
||||
removed_count,
|
||||
removed_files,
|
||||
skipped_files,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_extension_from_secure_preferences(
|
||||
path: &Path,
|
||||
extension_id: &str,
|
||||
@@ -427,3 +558,69 @@ fn cleanup_sessions_directory(path: &Path) -> Result<bool, std::io::Error> {
|
||||
|
||||
Ok(deleted_any)
|
||||
}
|
||||
|
||||
fn remove_bookmark_backups(profile_path: &Path) -> Result<bool, String> {
|
||||
let mut deleted_any = false;
|
||||
for backup_name in ["Bookmarks.bak", "Bookmark.bak"] {
|
||||
let backup_path = profile_path.join(backup_name);
|
||||
if !backup_path.is_file() {
|
||||
continue;
|
||||
}
|
||||
fs::remove_file(&backup_path)
|
||||
.map_err(|error| format!("Failed to delete {}: {error}", backup_path.display()))?;
|
||||
deleted_any = true;
|
||||
}
|
||||
|
||||
Ok(deleted_any)
|
||||
}
|
||||
|
||||
fn resolve_bookmarks_path(profile_path: &Path) -> Option<PathBuf> {
|
||||
["Bookmarks", "Bookmark"]
|
||||
.into_iter()
|
||||
.map(|name| profile_path.join(name))
|
||||
.find(|path| path.is_file())
|
||||
}
|
||||
|
||||
fn remove_matching_bookmarks(value: &mut Value, target_url: &str) -> usize {
|
||||
match value {
|
||||
Value::Object(object) => {
|
||||
let mut removed_count = 0;
|
||||
|
||||
if let Some(children) = object.get_mut("children").and_then(Value::as_array_mut) {
|
||||
let mut index = 0;
|
||||
while index < children.len() {
|
||||
let matches_url = children[index]
|
||||
.as_object()
|
||||
.map(|child| {
|
||||
child.get("type").and_then(Value::as_str) == Some("url")
|
||||
&& child.get("url").and_then(Value::as_str) == Some(target_url)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if matches_url {
|
||||
children.remove(index);
|
||||
removed_count += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
removed_count += remove_matching_bookmarks(&mut children[index], target_url);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (key, child) in object.iter_mut() {
|
||||
if key == "children" {
|
||||
continue;
|
||||
}
|
||||
removed_count += remove_matching_bookmarks(child, target_url);
|
||||
}
|
||||
|
||||
removed_count
|
||||
}
|
||||
Value::Array(array) => array
|
||||
.iter_mut()
|
||||
.map(|item| remove_matching_bookmarks(item, target_url))
|
||||
.sum(),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ pub fn run() {
|
||||
commands::delete_custom_browser_config,
|
||||
commands::open_browser_profile,
|
||||
commands::cleanup_history_files,
|
||||
commands::remove_extensions
|
||||
commands::remove_extensions,
|
||||
commands::remove_bookmarks
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -122,6 +122,13 @@ pub struct RemoveExtensionsInput {
|
||||
pub removals: Vec<ExtensionRemovalRequest>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoveBookmarksInput {
|
||||
pub browser_id: String,
|
||||
pub removals: Vec<BookmarkRemovalRequest>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExtensionRemovalRequest {
|
||||
@@ -129,12 +136,25 @@ pub struct ExtensionRemovalRequest {
|
||||
pub profile_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BookmarkRemovalRequest {
|
||||
pub url: String,
|
||||
pub profile_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoveExtensionsResponse {
|
||||
pub results: Vec<RemoveExtensionResult>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoveBookmarksResponse {
|
||||
pub results: Vec<RemoveBookmarkResult>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoveExtensionResult {
|
||||
@@ -145,6 +165,17 @@ pub struct RemoveExtensionResult {
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoveBookmarkResult {
|
||||
pub url: String,
|
||||
pub profile_id: String,
|
||||
pub removed_count: usize,
|
||||
pub removed_files: Vec<String>,
|
||||
pub skipped_files: Vec<String>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AssociatedProfileSummary {
|
||||
|
||||
Reference in New Issue
Block a user