support history
This commit is contained in:
@@ -20,6 +20,7 @@ pub struct BrowserView {
|
||||
pub extensions: Vec<ExtensionSummary>,
|
||||
pub bookmarks: Vec<BookmarkSummary>,
|
||||
pub password_sites: Vec<PasswordSiteSummary>,
|
||||
pub history_domains: Vec<HistoryDomainSummary>,
|
||||
pub stats: BrowserStats,
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ pub struct BrowserStats {
|
||||
pub extension_count: usize,
|
||||
pub bookmark_count: usize,
|
||||
pub password_site_count: usize,
|
||||
pub history_domain_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -75,6 +77,15 @@ pub struct PasswordSiteSummary {
|
||||
pub profiles: Vec<AssociatedProfileSummary>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HistoryDomainSummary {
|
||||
pub domain: String,
|
||||
pub visit_count: i64,
|
||||
pub profile_ids: Vec<String>,
|
||||
pub profiles: Vec<AssociatedProfileSummary>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AssociatedProfileSummary {
|
||||
@@ -183,3 +194,10 @@ pub struct TempPasswordSite {
|
||||
pub profile_ids: BTreeSet<String>,
|
||||
pub profiles: BTreeMap<String, AssociatedProfileSummary>,
|
||||
}
|
||||
|
||||
pub struct TempHistoryDomain {
|
||||
pub domain: String,
|
||||
pub visit_count: i64,
|
||||
pub profile_ids: BTreeSet<String>,
|
||||
pub profiles: BTreeMap<String, AssociatedProfileSummary>,
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ use crate::{
|
||||
config_store,
|
||||
models::{
|
||||
AssociatedProfileSummary, BookmarkAssociatedProfileSummary, BookmarkSummary,
|
||||
BrowserConfigEntry, BrowserStats, BrowserView, ExtensionSummary, PasswordSiteSummary,
|
||||
ProfileSummary, ScanResponse, TempBookmark, TempExtension, TempPasswordSite,
|
||||
BrowserConfigEntry, BrowserStats, BrowserView, ExtensionSummary, HistoryDomainSummary,
|
||||
PasswordSiteSummary, ProfileSummary, ScanResponse, TempBookmark, TempExtension,
|
||||
TempHistoryDomain, TempPasswordSite,
|
||||
},
|
||||
utils::{
|
||||
copy_sqlite_database_to_temp, first_non_empty, load_image_as_data_url, read_json_file,
|
||||
@@ -47,6 +48,7 @@ fn scan_browser(config: BrowserConfigEntry) -> Option<BrowserView> {
|
||||
let mut extensions = BTreeMap::<String, TempExtension>::new();
|
||||
let mut bookmarks = BTreeMap::<String, TempBookmark>::new();
|
||||
let mut password_sites = BTreeMap::<String, TempPasswordSite>::new();
|
||||
let mut history_domains = BTreeMap::<String, TempHistoryDomain>::new();
|
||||
|
||||
for profile_id in profile_ids {
|
||||
let profile_path = root.join(&profile_id);
|
||||
@@ -60,6 +62,7 @@ fn scan_browser(config: BrowserConfigEntry) -> Option<BrowserView> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -93,6 +96,15 @@ fn scan_browser(config: BrowserConfigEntry) -> Option<BrowserView> {
|
||||
profiles: entry.profiles.into_values().collect(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(BrowserView {
|
||||
browser_id: config.id,
|
||||
@@ -105,11 +117,13 @@ fn scan_browser(config: BrowserConfigEntry) -> Option<BrowserView> {
|
||||
extension_count: extensions.len(),
|
||||
bookmark_count: bookmarks.len(),
|
||||
password_site_count: password_sites.len(),
|
||||
history_domain_count: history_domains.len(),
|
||||
},
|
||||
profiles,
|
||||
extensions: sort_extensions(extensions),
|
||||
bookmarks: sort_bookmarks(bookmarks),
|
||||
password_sites: sort_password_sites(password_sites),
|
||||
history_domains: sort_history_domains(history_domains),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -609,6 +623,71 @@ fn scan_password_sites_for_profile(
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_history_domains_for_profile(
|
||||
profile_path: &Path,
|
||||
profile: &ProfileSummary,
|
||||
history_domains: &mut BTreeMap<String, TempHistoryDomain>,
|
||||
) {
|
||||
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<String>>(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<String> {
|
||||
let candidate = [signon_realm, origin_url]
|
||||
.into_iter()
|
||||
@@ -640,3 +719,16 @@ fn sort_password_sites(mut password_sites: Vec<PasswordSiteSummary>) -> Vec<Pass
|
||||
});
|
||||
password_sites
|
||||
}
|
||||
|
||||
fn sort_history_domains(
|
||||
mut history_domains: Vec<HistoryDomainSummary>,
|
||||
) -> Vec<HistoryDomainSummary> {
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user