diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 1494a5e..554b4c5 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,8 +1,15 @@ -use std::{path::PathBuf, process::Command}; +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, +}; use crate::{ config_store, - models::{BrowserConfigListResponse, CreateCustomBrowserConfigInput, ScanResponse}, + models::{ + BrowserConfigListResponse, CleanupHistoryInput, CleanupHistoryResponse, + CleanupHistoryResult, CreateCustomBrowserConfigInput, ScanResponse, + }, scanner, }; use tauri::AppHandle; @@ -61,6 +68,31 @@ pub fn open_browser_profile( spawn_browser_process(executable_path, user_data_dir, profile_id) } +#[tauri::command] +pub fn cleanup_history_files( + app: AppHandle, + input: CleanupHistoryInput, +) -> Result { + 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 profile_id in input.profile_ids { + let profile_path = user_data_dir.join(&profile_id); + let result = cleanup_profile_history_files(&profile_path, &profile_id); + results.push(result); + } + + Ok(CleanupHistoryResponse { results }) +} + fn spawn_browser_process( executable_path: PathBuf, user_data_dir: PathBuf, @@ -79,3 +111,56 @@ fn spawn_browser_process( ) }) } + +fn cleanup_profile_history_files(profile_path: &Path, profile_id: &str) -> CleanupHistoryResult { + if !profile_path.is_dir() { + return CleanupHistoryResult { + profile_id: profile_id.to_string(), + deleted_files: Vec::new(), + skipped_files: Vec::new(), + error: Some(format!( + "Profile directory does not exist: {}", + profile_path.display() + )), + }; + } + + let mut deleted_files = Vec::new(); + let mut skipped_files = Vec::new(); + + for file_name in ["History", "Top Sites", "Visited Links"] { + let file_path = profile_path.join(file_name); + if !file_path.exists() { + skipped_files.push(file_name.to_string()); + continue; + } + + if let Err(error) = fs::remove_file(&file_path) { + return CleanupHistoryResult { + profile_id: profile_id.to_string(), + deleted_files, + skipped_files, + error: Some(format!("Failed to delete {}: {error}", file_path.display())), + }; + } + + deleted_files.push(file_name.to_string()); + remove_sidecar_files(&file_path); + } + + CleanupHistoryResult { + profile_id: profile_id.to_string(), + deleted_files, + skipped_files, + error: None, + } +} + +fn remove_sidecar_files(path: &Path) { + for suffix in ["-journal", "-wal", "-shm"] { + let sidecar = PathBuf::from(format!("{}{}", path.display(), suffix)); + if sidecar.is_file() { + let _ = fs::remove_file(sidecar); + } + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 576c485..524662f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -15,7 +15,8 @@ pub fn run() { commands::list_browser_configs, commands::create_custom_browser_config, commands::delete_custom_browser_config, - commands::open_browser_profile + commands::open_browser_profile, + commands::cleanup_history_files ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 61e1cae..abf1f50 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -20,7 +20,6 @@ pub struct BrowserView { pub extensions: Vec, pub bookmarks: Vec, pub password_sites: Vec, - pub history_domains: Vec, pub stats: BrowserStats, } @@ -31,7 +30,7 @@ pub struct BrowserStats { pub extension_count: usize, pub bookmark_count: usize, pub password_site_count: usize, - pub history_domain_count: usize, + pub history_cleanup_profile_count: usize, } #[derive(Serialize)] @@ -46,6 +45,7 @@ pub struct ProfileSummary { pub default_avatar_stroke_color: Option, pub avatar_label: String, pub path: String, + pub history_cleanup: HistoryCleanupSummary, } #[derive(Serialize)] @@ -77,13 +77,41 @@ pub struct PasswordSiteSummary { pub profiles: Vec, } +#[derive(Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct HistoryCleanupSummary { + pub history: CleanupFileStatus, + pub top_sites: CleanupFileStatus, + pub visited_links: CleanupFileStatus, +} + +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum CleanupFileStatus { + Found, + Missing, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CleanupHistoryInput { + pub browser_id: String, + pub profile_ids: Vec, +} + #[derive(Serialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryDomainSummary { - pub domain: String, - pub visit_count: i64, - pub profile_ids: Vec, - pub profiles: Vec, +pub struct CleanupHistoryResponse { + pub results: Vec, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CleanupHistoryResult { + pub profile_id: String, + pub deleted_files: Vec, + pub skipped_files: Vec, + pub error: Option, } #[derive(Serialize, Clone)] @@ -194,10 +222,3 @@ pub struct TempPasswordSite { pub profile_ids: BTreeSet, pub profiles: BTreeMap, } - -pub struct TempHistoryDomain { - pub domain: String, - pub visit_count: i64, - pub profile_ids: BTreeSet, - pub profiles: BTreeMap, -} diff --git a/src-tauri/src/scanner.rs b/src-tauri/src/scanner.rs index 9d73313..34f1f63 100644 --- a/src-tauri/src/scanner.rs +++ b/src-tauri/src/scanner.rs @@ -11,9 +11,9 @@ use crate::{ config_store, models::{ AssociatedProfileSummary, BookmarkAssociatedProfileSummary, BookmarkSummary, - BrowserConfigEntry, BrowserStats, BrowserView, ExtensionSummary, HistoryDomainSummary, - PasswordSiteSummary, ProfileSummary, ScanResponse, TempBookmark, TempExtension, - TempHistoryDomain, TempPasswordSite, + BrowserConfigEntry, BrowserStats, BrowserView, CleanupFileStatus, ExtensionSummary, + HistoryCleanupSummary, PasswordSiteSummary, ProfileSummary, ScanResponse, TempBookmark, + TempExtension, TempPasswordSite, }, utils::{ copy_sqlite_database_to_temp, first_non_empty, load_image_as_data_url, read_json_file, @@ -48,7 +48,6 @@ fn scan_browser(config: BrowserConfigEntry) -> Option { let mut extensions = BTreeMap::::new(); let mut bookmarks = BTreeMap::::new(); let mut password_sites = BTreeMap::::new(); - let mut history_domains = BTreeMap::::new(); for profile_id in profile_ids { let profile_path = root.join(&profile_id); @@ -62,7 +61,6 @@ fn scan_browser(config: BrowserConfigEntry) -> Option { scan_extensions_for_profile(&profile_path, &profile_summary, &mut extensions); scan_bookmarks_for_profile(&profile_path, &profile_summary, &mut bookmarks); scan_password_sites_for_profile(&profile_path, &profile_summary, &mut password_sites); - scan_history_domains_for_profile(&profile_path, &profile_summary, &mut history_domains); profiles.push(profile_summary); } @@ -96,15 +94,15 @@ fn scan_browser(config: BrowserConfigEntry) -> Option { profiles: entry.profiles.into_values().collect(), }) .collect::>(); - let history_domains = history_domains - .into_values() - .map(|entry| HistoryDomainSummary { - domain: entry.domain, - visit_count: entry.visit_count, - profile_ids: entry.profile_ids.into_iter().collect(), - profiles: entry.profiles.into_values().collect(), + let history_cleanup_profile_count = profiles + .iter() + .filter(|profile| { + let cleanup = &profile.history_cleanup; + cleanup.history == CleanupFileStatus::Found + || cleanup.top_sites == CleanupFileStatus::Found + || cleanup.visited_links == CleanupFileStatus::Found }) - .collect::>(); + .count(); Some(BrowserView { browser_id: config.id, @@ -117,13 +115,12 @@ fn scan_browser(config: BrowserConfigEntry) -> Option { extension_count: extensions.len(), bookmark_count: bookmarks.len(), password_site_count: password_sites.len(), - history_domain_count: history_domains.len(), + history_cleanup_profile_count, }, profiles, extensions: sort_extensions(extensions), bookmarks: sort_bookmarks(bookmarks), password_sites: sort_password_sites(password_sites), - history_domains: sort_history_domains(history_domains), }) } @@ -189,6 +186,23 @@ fn build_profile_summary( default_avatar_stroke_color, avatar_label, path: profile_path.display().to_string(), + history_cleanup: scan_history_cleanup_status(profile_path), + } +} + +fn scan_history_cleanup_status(profile_path: &Path) -> HistoryCleanupSummary { + HistoryCleanupSummary { + history: cleanup_file_status(&profile_path.join("History")), + top_sites: cleanup_file_status(&profile_path.join("Top Sites")), + visited_links: cleanup_file_status(&profile_path.join("Visited Links")), + } +} + +fn cleanup_file_status(path: &Path) -> CleanupFileStatus { + if path.is_file() { + CleanupFileStatus::Found + } else { + CleanupFileStatus::Missing } } @@ -623,71 +637,6 @@ fn scan_password_sites_for_profile( } } -fn scan_history_domains_for_profile( - profile_path: &Path, - profile: &ProfileSummary, - history_domains: &mut BTreeMap, -) { - let history_path = profile_path.join("History"); - if !history_path.is_file() { - return; - } - - let Some(temp_copy) = copy_sqlite_database_to_temp(&history_path) else { - return; - }; - let Ok(connection) = Connection::open_with_flags( - temp_copy.path(), - OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX, - ) else { - return; - }; - - let Ok(mut statement) = connection - .prepare("SELECT url, visit_count FROM urls WHERE hidden = 0 AND visit_count > 0") - else { - return; - }; - let Ok(rows) = statement.query_map([], |row| { - Ok((row.get::<_, Option>(0)?, row.get::<_, i64>(1)?)) - }) else { - return; - }; - - for row in rows.flatten() { - let Some(url) = row.0.as_deref() else { - continue; - }; - let Some(domain) = domain_from_url(url) else { - continue; - }; - - let entry = history_domains - .entry(domain.clone()) - .or_insert_with(|| TempHistoryDomain { - domain: domain.clone(), - visit_count: 0, - profile_ids: BTreeSet::new(), - profiles: BTreeMap::new(), - }); - - entry.visit_count += row.1.max(0); - entry.profile_ids.insert(profile.id.clone()); - entry - .profiles - .entry(profile.id.clone()) - .or_insert_with(|| AssociatedProfileSummary { - id: profile.id.clone(), - name: profile.name.clone(), - avatar_data_url: profile.avatar_data_url.clone(), - avatar_icon: profile.avatar_icon.clone(), - default_avatar_fill_color: profile.default_avatar_fill_color, - default_avatar_stroke_color: profile.default_avatar_stroke_color, - avatar_label: profile.avatar_label.clone(), - }); - } -} - fn normalize_login_site(origin_url: Option<&str>, signon_realm: Option<&str>) -> Option { let candidate = [signon_realm, origin_url] .into_iter() @@ -719,16 +668,3 @@ fn sort_password_sites(mut password_sites: Vec) -> Vec, -) -> Vec { - history_domains.sort_by(|left, right| { - right - .visit_count - .cmp(&left.visit_count) - .then_with(|| left.domain.to_lowercase().cmp(&right.domain.to_lowercase())) - .then_with(|| left.domain.cmp(&right.domain)) - }); - history_domains -} diff --git a/src/App.vue b/src/App.vue index 2b846d4..c0c23fa 100644 --- a/src/App.vue +++ b/src/App.vue @@ -16,13 +16,17 @@ const { configsLoading, createConfigForm, createCustomBrowserConfig, + cleanupHistoryError, + cleanupHistoryForProfile, + cleanupHistoryResults, + cleanupHistorySelectedProfiles, + cleanupSelectedHistoryProfiles, currentBrowser, deleteCustomBrowserConfig, - domainFromUrl, error, extensionMonogram, extensionSortKey, - historyDomainSortKey, + historyCleanupBusy, isDeletingConfig, isOpeningProfile, loading, @@ -39,13 +43,13 @@ const { selectedBrowserId, showBookmarkProfilesModal, showExtensionProfilesModal, - showHistoryDomainProfilesModal, showPasswordSiteProfilesModal, sortedBookmarks, sortedExtensions, - sortedHistoryDomains, sortedPasswordSites, sortedProfiles, + toggleAllHistoryProfiles, + toggleHistoryProfile, closeAssociatedProfilesModal, } = useBrowserManager(); @@ -109,29 +113,32 @@ const { :extension-sort-key="extensionSortKey" :bookmark-sort-key="bookmarkSortKey" :password-site-sort-key="passwordSiteSortKey" - :history-domain-sort-key="historyDomainSortKey" :sorted-profiles="sortedProfiles" :sorted-extensions="sortedExtensions" :sorted-bookmarks="sortedBookmarks" :sorted-password-sites="sortedPasswordSites" - :sorted-history-domains="sortedHistoryDomains" + :history-selected-profile-ids="cleanupHistorySelectedProfiles" + :cleanup-history-busy="historyCleanupBusy" + :cleanup-history-error="cleanupHistoryError" + :cleanup-history-results="cleanupHistoryResults" :open-profile-error="openProfileError" :section-count="sectionCount" :is-opening-profile="isOpeningProfile" :extension-monogram="extensionMonogram" - :domain-from-url="domainFromUrl" :associated-profiles-modal="associatedProfilesModal" @update:active-section="activeSection = $event" @update:profile-sort-key="profileSortKey = $event" @update:extension-sort-key="extensionSortKey = $event" @update:bookmark-sort-key="bookmarkSortKey = $event" @update:password-site-sort-key="passwordSiteSortKey = $event" - @update:history-domain-sort-key="historyDomainSortKey = $event" @open-profile="(browserId, profileId) => openBrowserProfile(browserId, profileId)" @show-extension-profiles="showExtensionProfilesModal" @show-bookmark-profiles="showBookmarkProfilesModal" @show-password-site-profiles="showPasswordSiteProfilesModal" - @show-history-domain-profiles="showHistoryDomainProfilesModal" + @toggle-history-profile="toggleHistoryProfile" + @toggle-all-history-profiles="toggleAllHistoryProfiles" + @cleanup-selected-history="cleanupSelectedHistoryProfiles" + @cleanup-history-for-profile="cleanupHistoryForProfile" @close-associated-profiles="closeAssociatedProfilesModal" /> diff --git a/src/components/browser-data/BrowserDataView.vue b/src/components/browser-data/BrowserDataView.vue index 1edd63c..b8facb3 100644 --- a/src/components/browser-data/BrowserDataView.vue +++ b/src/components/browser-data/BrowserDataView.vue @@ -6,14 +6,14 @@ import type { BookmarkSortKey, BrowserView, ExtensionSortKey, - HistoryDomainSortKey, + CleanupHistoryResult, PasswordSiteSortKey, ProfileSortKey, } from "../../types/browser"; import AssociatedProfilesModal from "./AssociatedProfilesModal.vue"; import BookmarksList from "./BookmarksList.vue"; import ExtensionsList from "./ExtensionsList.vue"; -import HistoryDomainsList from "./HistoryDomainsList.vue"; +import HistoryCleanupList from "./HistoryCleanupList.vue"; import PasswordSitesList from "./PasswordSitesList.vue"; import ProfilesList from "./ProfilesList.vue"; @@ -24,17 +24,18 @@ defineProps<{ extensionSortKey: ExtensionSortKey; bookmarkSortKey: BookmarkSortKey; passwordSiteSortKey: PasswordSiteSortKey; - historyDomainSortKey: HistoryDomainSortKey; sortedProfiles: BrowserView["profiles"]; sortedExtensions: BrowserView["extensions"]; sortedBookmarks: BrowserView["bookmarks"]; sortedPasswordSites: BrowserView["passwordSites"]; - sortedHistoryDomains: BrowserView["historyDomains"]; + historySelectedProfileIds: string[]; + cleanupHistoryBusy: boolean; + cleanupHistoryError: string; + cleanupHistoryResults: CleanupHistoryResult[]; openProfileError: string; sectionCount: (section: ActiveSection) => number; isOpeningProfile: (browserId: string, profileId: string) => boolean; extensionMonogram: (name: string) => string; - domainFromUrl: (url: string) => string; associatedProfilesModal: { title: string; browserId: string; @@ -49,12 +50,14 @@ const emit = defineEmits<{ "update:extensionSortKey": [value: ExtensionSortKey]; "update:bookmarkSortKey": [value: BookmarkSortKey]; "update:passwordSiteSortKey": [value: PasswordSiteSortKey]; - "update:historyDomainSortKey": [value: HistoryDomainSortKey]; openProfile: [browserId: string, profileId: string]; showExtensionProfiles: [extensionId: string]; showBookmarkProfiles: [url: string]; showPasswordSiteProfiles: [url: string]; - showHistoryDomainProfiles: [domain: string]; + toggleHistoryProfile: [profileId: string]; + toggleAllHistoryProfiles: []; + cleanupSelectedHistory: []; + cleanupHistoryForProfile: [profileId: string]; closeAssociatedProfiles: []; }>(); @@ -146,12 +149,19 @@ const emit = defineEmits<{ @show-profiles="emit('showPasswordSiteProfiles', $event)" /> - diff --git a/src/components/browser-data/HistoryCleanupList.vue b/src/components/browser-data/HistoryCleanupList.vue new file mode 100644 index 0000000..74e963c --- /dev/null +++ b/src/components/browser-data/HistoryCleanupList.vue @@ -0,0 +1,391 @@ + + + + + diff --git a/src/components/browser-data/HistoryDomainsList.vue b/src/components/browser-data/HistoryDomainsList.vue deleted file mode 100644 index 0d129f0..0000000 --- a/src/components/browser-data/HistoryDomainsList.vue +++ /dev/null @@ -1,165 +0,0 @@ - - - - - diff --git a/src/composables/useBrowserManager.ts b/src/composables/useBrowserManager.ts index c91ee57..279ced8 100644 --- a/src/composables/useBrowserManager.ts +++ b/src/composables/useBrowserManager.ts @@ -2,13 +2,7 @@ import { computed, onMounted, ref, watch } from "vue"; import { invoke } from "@tauri-apps/api/core"; import { open } from "@tauri-apps/plugin-dialog"; -import { - sortBookmarks, - sortExtensions, - sortHistoryDomains, - sortPasswordSites, - sortProfiles, -} from "../utils/sort"; +import { sortBookmarks, sortExtensions, sortPasswordSites, sortProfiles } from "../utils/sort"; import type { ActiveSection, AppPage, @@ -18,9 +12,10 @@ import type { BrowserConfigEntry, BrowserConfigListResponse, BrowserView, + CleanupHistoryInput, + CleanupHistoryResponse, CreateCustomBrowserConfigInput, ExtensionSortKey, - HistoryDomainSortKey, PasswordSiteSortKey, ProfileSortKey, ScanResponse, @@ -56,7 +51,10 @@ export function useBrowserManager() { const extensionSortKey = ref("name"); const bookmarkSortKey = ref("title"); const passwordSiteSortKey = ref("domain"); - const historyDomainSortKey = ref("visits"); + const cleanupHistorySelectedProfiles = ref([]); + const historyCleanupBusy = ref(false); + const cleanupHistoryError = ref(""); + const cleanupHistoryResults = ref([]); const browsers = computed(() => response.value.browsers); const currentBrowser = computed( @@ -78,9 +76,6 @@ export function useBrowserManager() { const sortedPasswordSites = computed(() => sortPasswordSites(currentBrowser.value?.passwordSites ?? [], passwordSiteSortKey.value), ); - const sortedHistoryDomains = computed(() => - sortHistoryDomains(currentBrowser.value?.historyDomains ?? [], historyDomainSortKey.value), - ); watch( browsers, @@ -104,6 +99,9 @@ export function useBrowserManager() { watch(selectedBrowserId, () => { openProfileError.value = ""; associatedProfilesModal.value = null; + cleanupHistorySelectedProfiles.value = []; + cleanupHistoryResults.value = []; + cleanupHistoryError.value = ""; }); async function loadBrowserConfigs() { @@ -284,21 +282,13 @@ export function useBrowserManager() { return name.trim().slice(0, 1).toUpperCase() || "?"; } - function domainFromUrl(url: string) { - try { - return new URL(url).hostname; - } catch { - return url; - } - } - function sectionCount(section: ActiveSection) { if (!currentBrowser.value) return 0; if (section === "profiles") return currentBrowser.value.profiles.length; if (section === "extensions") return currentBrowser.value.extensions.length; if (section === "bookmarks") return currentBrowser.value.bookmarks.length; if (section === "passwords") return currentBrowser.value.passwordSites.length; - return currentBrowser.value.historyDomains.length; + return currentBrowser.value.stats.historyCleanupProfileCount; } function showExtensionProfilesModal(extensionId: string) { @@ -334,17 +324,78 @@ export function useBrowserManager() { }; } - function showHistoryDomainProfilesModal(domain: string) { - const historyDomain = currentBrowser.value?.historyDomains.find( - (item) => item.domain === domain, - ); - if (!historyDomain || !currentBrowser.value) return; - associatedProfilesModal.value = { - title: `${historyDomain.domain} Profiles`, - browserId: currentBrowser.value.browserId, - profiles: historyDomain.profiles, - isBookmark: false, - }; + function toggleHistoryProfile(profileId: string) { + if (cleanupHistorySelectedProfiles.value.includes(profileId)) { + cleanupHistorySelectedProfiles.value = cleanupHistorySelectedProfiles.value.filter( + (selectedId) => selectedId !== profileId, + ); + return; + } + + cleanupHistorySelectedProfiles.value = [ + ...cleanupHistorySelectedProfiles.value, + profileId, + ]; + } + + function toggleAllHistoryProfiles() { + const current = currentBrowser.value; + if (!current) return; + + const selectableIds = current.profiles + .filter((profile) => { + const cleanup = profile.historyCleanup; + return ( + cleanup.history === "found" || + cleanup.topSites === "found" || + cleanup.visitedLinks === "found" + ); + }) + .map((profile) => profile.id); + + const allSelected = + selectableIds.length > 0 && + selectableIds.every((profileId) => + cleanupHistorySelectedProfiles.value.includes(profileId), + ); + + cleanupHistorySelectedProfiles.value = allSelected ? [] : selectableIds; + } + + async function cleanupHistoryProfiles(profileIds: string[]) { + if (!currentBrowser.value || profileIds.length === 0) return; + + historyCleanupBusy.value = true; + cleanupHistoryError.value = ""; + cleanupHistoryResults.value = []; + + try { + const input: CleanupHistoryInput = { + browserId: currentBrowser.value.browserId, + profileIds, + }; + const result = await invoke("cleanup_history_files", { input }); + cleanupHistoryResults.value = result.results; + cleanupHistorySelectedProfiles.value = cleanupHistorySelectedProfiles.value.filter( + (profileId) => !profileIds.includes(profileId), + ); + await scanBrowsers(); + } catch (cleanupErrorValue) { + cleanupHistoryError.value = + cleanupErrorValue instanceof Error + ? cleanupErrorValue.message + : "Failed to clean history files."; + } finally { + historyCleanupBusy.value = false; + } + } + + async function cleanupSelectedHistoryProfiles() { + await cleanupHistoryProfiles(cleanupHistorySelectedProfiles.value); + } + + async function cleanupHistoryForProfile(profileId: string) { + await cleanupHistoryProfiles([profileId]); } function closeAssociatedProfilesModal() { @@ -369,11 +420,15 @@ export function useBrowserManager() { createCustomBrowserConfig, currentBrowser, deleteCustomBrowserConfig, - domainFromUrl, error, extensionMonogram, extensionSortKey, - historyDomainSortKey, + cleanupHistoryError, + cleanupHistoryForProfile, + cleanupHistoryResults, + cleanupHistorySelectedProfiles, + cleanupSelectedHistoryProfiles, + historyCleanupBusy, isDeletingConfig, isOpeningProfile, loading, @@ -390,13 +445,13 @@ export function useBrowserManager() { selectedBrowserId, showBookmarkProfilesModal, showExtensionProfilesModal, - showHistoryDomainProfilesModal, showPasswordSiteProfilesModal, sortedBookmarks, sortedExtensions, - sortedHistoryDomains, sortedPasswordSites, sortedProfiles, + toggleAllHistoryProfiles, + toggleHistoryProfile, closeAssociatedProfilesModal, }; } diff --git a/src/types/browser.ts b/src/types/browser.ts index e47fac6..bca2213 100644 --- a/src/types/browser.ts +++ b/src/types/browser.ts @@ -3,7 +3,7 @@ export type BrowserStats = { extensionCount: number; bookmarkCount: number; passwordSiteCount: number; - historyDomainCount: number; + historyCleanupProfileCount: number; }; export type ProfileSummary = { @@ -16,6 +16,7 @@ export type ProfileSummary = { defaultAvatarStrokeColor: number | null; avatarLabel: string; path: string; + historyCleanup: HistoryCleanupSummary; }; export type ExtensionSummary = { @@ -41,11 +42,28 @@ export type PasswordSiteSummary = { profiles: AssociatedProfileSummary[]; }; -export type HistoryDomainSummary = { - domain: string; - visitCount: number; +export type HistoryCleanupSummary = { + history: CleanupFileStatus; + topSites: CleanupFileStatus; + visitedLinks: CleanupFileStatus; +}; + +export type CleanupFileStatus = "found" | "missing"; + +export type CleanupHistoryInput = { + browserId: string; profileIds: string[]; - profiles: AssociatedProfileSummary[]; +}; + +export type CleanupHistoryResult = { + profileId: string; + deletedFiles: string[]; + skippedFiles: string[]; + error: string | null; +}; + +export type CleanupHistoryResponse = { + results: CleanupHistoryResult[]; }; export type AssociatedProfileSummary = { @@ -73,7 +91,6 @@ export type ProfileSortKey = "name" | "email" | "id"; export type ExtensionSortKey = "name" | "id"; export type BookmarkSortKey = "title" | "url"; export type PasswordSiteSortKey = "domain" | "url"; -export type HistoryDomainSortKey = "visits" | "domain"; export type AssociatedProfileSortKey = "id" | "name"; export type ActiveSection = "profiles" | "extensions" | "bookmarks" | "passwords" | "history"; export type AppPage = "browserData" | "configuration"; @@ -111,7 +128,6 @@ export type BrowserView = { extensions: ExtensionSummary[]; bookmarks: BookmarkSummary[]; passwordSites: PasswordSiteSummary[]; - historyDomains: HistoryDomainSummary[]; stats: BrowserStats; }; diff --git a/src/utils/sort.ts b/src/utils/sort.ts index 01756d8..ea52846 100644 --- a/src/utils/sort.ts +++ b/src/utils/sort.ts @@ -3,8 +3,6 @@ import type { BookmarkSummary, ExtensionSortKey, ExtensionSummary, - HistoryDomainSortKey, - HistoryDomainSummary, PasswordSiteSortKey, PasswordSiteSummary, AssociatedProfileSortKey, @@ -90,16 +88,6 @@ export function sortPasswordSites(items: PasswordSiteSummary[], sortKey: Passwor }); } -export function sortHistoryDomains(items: HistoryDomainSummary[], sortKey: HistoryDomainSortKey) { - const historyDomains = [...items]; - return historyDomains.sort((left, right) => { - if (sortKey === "domain") { - return compareOptionalText(left.domain, right.domain) || right.visitCount - left.visitCount; - } - return right.visitCount - left.visitCount || compareOptionalText(left.domain, right.domain); - }); -} - export function sortAssociatedProfiles( items: (AssociatedProfileSummary | BookmarkAssociatedProfileSummary)[], sortKey: AssociatedProfileSortKey,