1use crate::api::matcher::{self, search_frecent, SearchParams};
8pub use crate::api::places_api::places_api_new;
9pub use crate::error::{warn, Result};
10pub use crate::error::{ApiResult, PlacesApiError};
11#[cfg(all(feature = "glean-sym", any(target_os = "android", target_os = "ios")))]
12use crate::glean_metrics::places_manager;
13pub use crate::import::common::HistoryMigrationResult;
14use crate::import::import_ios_history;
15use crate::storage;
16use crate::storage::bookmarks;
17pub use crate::storage::bookmarks::BookmarkPosition;
18pub use crate::storage::history_metadata::{
19 DocumentType, HistoryHighlight, HistoryHighlightWeights, HistoryMetadata,
20 HistoryMetadataObservation, HistoryMetadataPageMissingBehavior,
21 NoteHistoryMetadataObservationOptions,
22};
23pub use crate::storage::RunMaintenanceMetrics;
24use crate::storage::{history, history_metadata};
25use crate::types::VisitTransitionSet;
26use crate::ConnectionType;
27use crate::VisitObservation;
28use crate::VisitType;
29use crate::{PlacesApi, PlacesDb};
30use error_support::handle_error;
31use interrupt_support::register_interrupt;
32pub use interrupt_support::SqlInterruptHandle;
33use parking_lot::Mutex;
34use std::sync::{Arc, Weak};
35pub use sync_guid::Guid;
36pub use types::Timestamp as PlacesTimestamp;
37pub use url::Url;
38
39const SKIP_ONE_PAGE_FRECENCY_THRESHOLD: i64 = 101 + 1;
41
42pub type InsertableBookmarkItem = crate::storage::bookmarks::InsertableItem;
45pub type InsertableBookmarkFolder = crate::storage::bookmarks::InsertableFolder;
46pub type InsertableBookmarkSeparator = crate::storage::bookmarks::InsertableSeparator;
47pub use crate::storage::bookmarks::InsertableBookmark;
48
49pub use crate::storage::bookmarks::BookmarkUpdateInfo;
50
51pub type BookmarkItem = crate::storage::bookmarks::fetch::Item;
53pub type BookmarkFolder = crate::storage::bookmarks::fetch::Folder;
54pub type BookmarkSeparator = crate::storage::bookmarks::fetch::Separator;
55pub use crate::storage::bookmarks::fetch::BookmarkData;
56
57lazy_static::lazy_static! {
62 static ref READ_WRITE_CONNECTIONS: Mutex<Vec<Weak<PlacesConnection>>> = Mutex::new(Vec::new());
63 static ref SYNC_CONNECTIONS: Mutex<Vec<Weak<PlacesConnection>>> = Mutex::new(Vec::new());
64}
65
66impl PlacesApi {
67 #[handle_error(crate::Error)]
68 pub fn new_connection(&self, conn_type: ConnectionType) -> ApiResult<Arc<PlacesConnection>> {
69 let db = self.open_connection(conn_type)?;
70 let connection = Arc::new(PlacesConnection::new(db));
71 register_interrupt(Arc::<PlacesConnection>::downgrade(&connection));
72 Ok(connection)
73 }
74}
75
76pub struct PlacesConnection {
77 db: Mutex<PlacesDb>,
78 interrupt_handle: Arc<SqlInterruptHandle>,
79}
80
81impl PlacesConnection {
82 pub fn new(db: PlacesDb) -> Self {
83 #[cfg(all(feature = "glean-sym", any(target_os = "android", target_os = "ios")))]
84 places_manager::connection_initialized.add(1);
85
86 Self {
87 interrupt_handle: db.new_interrupt_handle(),
88 db: Mutex::new(db),
89 }
90 }
91
92 fn with_conn<F, T>(&self, f: F) -> Result<T>
94 where
95 F: FnOnce(&PlacesDb) -> crate::error::Result<T>,
96 {
97 let conn = self.db.lock();
98 f(&conn)
99 }
100
101 pub fn new_interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
103 Arc::clone(&self.interrupt_handle)
104 }
105
106 #[handle_error(crate::Error)]
107 pub fn get_latest_history_metadata_for_url(
108 &self,
109 url: Url,
110 ) -> ApiResult<Option<HistoryMetadata>> {
111 self.with_conn(|conn| history_metadata::get_latest_for_url(conn, &url))
112 }
113
114 #[handle_error(crate::Error)]
115 pub fn get_history_metadata_between(
116 &self,
117 start: PlacesTimestamp,
118 end: PlacesTimestamp,
119 ) -> ApiResult<Vec<HistoryMetadata>> {
120 self.with_conn(|conn| {
121 history_metadata::get_between(conn, start.as_millis_i64(), end.as_millis_i64())
122 })
123 }
124
125 #[handle_error(crate::Error)]
126 pub fn get_history_metadata_since(
127 &self,
128 start: PlacesTimestamp,
129 ) -> ApiResult<Vec<HistoryMetadata>> {
130 self.with_conn(|conn| history_metadata::get_since(conn, start.as_millis_i64()))
131 }
132
133 #[handle_error(crate::Error)]
134 pub fn get_most_recent_history_metadata(&self, limit: i32) -> ApiResult<Vec<HistoryMetadata>> {
135 self.with_conn(|conn| history_metadata::get_most_recent(conn, limit))
136 }
137
138 #[handle_error(crate::Error)]
139 pub fn get_most_recent_search_entries_in_history_metadata(
140 &self,
141 limit: i32,
142 ) -> ApiResult<Vec<HistoryMetadata>> {
143 self.with_conn(|conn| history_metadata::get_most_recent_search_entries(conn, limit))
144 }
145
146 #[handle_error(crate::Error)]
147 pub fn query_history_metadata(
148 &self,
149 query: String,
150 limit: i32,
151 ) -> ApiResult<Vec<HistoryMetadata>> {
152 self.with_conn(|conn| history_metadata::query(conn, query.as_str(), limit))
153 }
154
155 #[handle_error(crate::Error)]
156 pub fn get_history_highlights(
157 &self,
158 weights: HistoryHighlightWeights,
159 limit: i32,
160 ) -> ApiResult<Vec<HistoryHighlight>> {
161 self.with_conn(|conn| history_metadata::get_highlights(conn, weights, limit))
162 }
163
164 #[handle_error(crate::Error)]
165 pub fn note_history_metadata_observation(
166 &self,
167 data: HistoryMetadataObservation,
168 options: NoteHistoryMetadataObservationOptions,
169 ) -> ApiResult<()> {
170 self.with_conn(|conn| history_metadata::apply_metadata_observation(conn, data, options))
172 }
173
174 #[handle_error(crate::Error)]
175 pub fn metadata_delete_older_than(&self, older_than: PlacesTimestamp) -> ApiResult<()> {
176 self.with_conn(|conn| history_metadata::delete_older_than(conn, older_than.as_millis_i64()))
177 }
178
179 #[handle_error(crate::Error)]
180 pub fn metadata_delete(
181 &self,
182 url: Url,
183 referrer_url: Option<Url>,
184 search_term: Option<String>,
185 ) -> ApiResult<()> {
186 self.with_conn(|conn| {
187 history_metadata::delete_metadata(
188 conn,
189 &url,
190 referrer_url.as_ref(),
191 search_term.as_deref(),
192 )
193 })
194 }
195
196 #[handle_error(crate::Error)]
197 pub fn metadata_delete_search_terms(&self) -> ApiResult<()> {
198 self.with_conn(history_metadata::delete_all_metadata_for_search)
199 }
200
201 #[handle_error(crate::Error)]
203 pub fn apply_observation(&self, visit: VisitObservation) -> ApiResult<()> {
204 self.with_conn(|conn| history::apply_observation(conn, visit))?;
205 Ok(())
206 }
207
208 #[handle_error(crate::Error)]
209 pub fn get_visited_urls_in_range(
210 &self,
211 start: PlacesTimestamp,
212 end: PlacesTimestamp,
213 include_remote: bool,
214 ) -> ApiResult<Vec<Url>> {
215 self.with_conn(|conn| {
216 let urls = history::get_visited_urls(conn, start, end, include_remote)?
217 .iter()
218 .filter_map(|s| Url::parse(s).ok())
220 .collect::<Vec<_>>();
221 Ok(urls)
222 })
223 }
224
225 #[handle_error(crate::Error)]
226 pub fn get_visit_infos(
227 &self,
228 start_date: PlacesTimestamp,
229 end_date: PlacesTimestamp,
230 exclude_types: VisitTransitionSet,
231 ) -> ApiResult<Vec<HistoryVisitInfo>> {
232 self.with_conn(|conn| history::get_visit_infos(conn, start_date, end_date, exclude_types))
233 }
234
235 #[handle_error(crate::Error)]
236 pub fn get_visit_count(&self, exclude_types: VisitTransitionSet) -> ApiResult<i64> {
237 self.with_conn(|conn| history::get_visit_count(conn, exclude_types))
238 }
239
240 #[handle_error(crate::Error)]
241 pub fn get_visit_count_for_host(
242 &self,
243 host: String,
244 before: PlacesTimestamp,
245 exclude_types: VisitTransitionSet,
246 ) -> ApiResult<i64> {
247 self.with_conn(|conn| {
248 history::get_visit_count_for_host(conn, host.as_str(), before, exclude_types)
249 })
250 }
251
252 #[handle_error(crate::Error)]
253 pub fn get_visit_page(
254 &self,
255 offset: i64,
256 count: i64,
257 exclude_types: VisitTransitionSet,
258 ) -> ApiResult<Vec<HistoryVisitInfo>> {
259 self.with_conn(|conn| history::get_visit_page(conn, offset, count, exclude_types))
260 }
261
262 #[handle_error(crate::Error)]
263 pub fn get_visit_page_with_bound(
264 &self,
265 bound: i64,
266 offset: i64,
267 count: i64,
268 exclude_types: VisitTransitionSet,
269 ) -> ApiResult<HistoryVisitInfosWithBound> {
270 self.with_conn(|conn| {
271 history::get_visit_page_with_bound(conn, bound, offset, count, exclude_types)
272 })
273 }
274
275 #[handle_error(crate::Error)]
279 pub fn get_visited(&self, urls: Vec<String>) -> ApiResult<Vec<bool>> {
280 let iter = urls.into_iter();
281 let mut result = vec![false; iter.len()];
282 let url_idxs = iter
283 .enumerate()
284 .filter_map(|(idx, s)| Url::parse(&s).ok().map(|url| (idx, url)))
285 .collect::<Vec<_>>();
286 self.with_conn(|conn| history::get_visited_into(conn, &url_idxs, &mut result))?;
287 Ok(result)
288 }
289
290 #[handle_error(crate::Error)]
291 pub fn delete_visits_for(&self, url: String) -> ApiResult<()> {
292 self.with_conn(|conn| {
293 let guid = match Url::parse(&url) {
294 Ok(url) => history::url_to_guid(conn, &url)?,
295 Err(e) => {
296 warn!("Invalid URL passed to places_delete_visits_for, {}", e);
297 history::href_to_guid(conn, url.clone().as_str())?
298 }
299 };
300 if let Some(guid) = guid {
301 history::delete_visits_for(conn, &guid)?;
302 }
303 Ok(())
304 })
305 }
306
307 #[handle_error(crate::Error)]
308 pub fn delete_visits_between(
309 &self,
310 start: PlacesTimestamp,
311 end: PlacesTimestamp,
312 ) -> ApiResult<()> {
313 self.with_conn(|conn| history::delete_visits_between(conn, start, end))
314 }
315
316 #[handle_error(crate::Error)]
317 pub fn delete_visit(&self, url: String, timestamp: PlacesTimestamp) -> ApiResult<()> {
318 self.with_conn(|conn| {
319 match Url::parse(&url) {
320 Ok(url) => {
321 history::delete_place_visit_at_time(conn, &url, timestamp)?;
322 }
323 Err(e) => {
324 warn!("Invalid URL passed to places_delete_visit, {}", e);
325 history::delete_place_visit_at_time_by_href(conn, url.as_str(), timestamp)?;
326 }
327 };
328 Ok(())
329 })
330 }
331
332 #[handle_error(crate::Error)]
333 pub fn get_top_frecent_site_infos(
334 &self,
335 num_items: i32,
336 threshold_option: FrecencyThresholdOption,
337 ) -> ApiResult<Vec<TopFrecentSiteInfo>> {
338 self.with_conn(|conn| {
339 crate::storage::history::get_top_frecent_site_infos(
340 conn,
341 num_items,
342 threshold_option.value(),
343 )
344 })
345 }
346 #[handle_error(crate::Error)]
349 pub fn delete_everything_history(&self) -> ApiResult<()> {
350 history::delete_everything(&self.db.lock())
351 }
352
353 #[handle_error(crate::Error)]
354 pub fn run_maintenance_prune(
355 &self,
356 db_size_limit: u32,
357 prune_limit: u32,
358 ) -> ApiResult<RunMaintenanceMetrics> {
359 #[cfg(all(feature = "glean-sym", any(target_os = "android", target_os = "ios")))]
360 let timer_id = places_manager::run_maintenance_prune_time_temp.start();
361 let res =
362 self.with_conn(|conn| storage::run_maintenance_prune(conn, db_size_limit, prune_limit));
363
364 #[cfg(all(feature = "glean-sym", any(target_os = "android", target_os = "ios")))]
365 places_manager::run_maintenance_prune_time_temp.stop_and_accumulate(timer_id);
366
367 res
368 }
369
370 #[handle_error(crate::Error)]
371 pub fn run_maintenance_vacuum(&self) -> ApiResult<()> {
372 #[cfg(all(feature = "glean-sym", any(target_os = "android", target_os = "ios")))]
373 let timer_id = places_manager::run_maintenance_vacuum_time_temp.start();
374 let res = self.with_conn(storage::run_maintenance_vacuum);
375
376 #[cfg(all(feature = "glean-sym", any(target_os = "android", target_os = "ios")))]
377 places_manager::run_maintenance_vacuum_time_temp.stop_and_accumulate(timer_id);
378
379 res
380 }
381
382 #[handle_error(crate::Error)]
383 pub fn run_maintenance_optimize(&self) -> ApiResult<()> {
384 #[cfg(all(feature = "glean-sym", any(target_os = "android", target_os = "ios")))]
385 let timer_id = places_manager::run_maintenance_optimize_time_temp.start();
386 let res = self.with_conn(storage::run_maintenance_optimize);
387
388 #[cfg(all(feature = "glean-sym", any(target_os = "android", target_os = "ios")))]
389 places_manager::run_maintenance_optimize_time_temp.stop_and_accumulate(timer_id);
390
391 res
392 }
393
394 #[handle_error(crate::Error)]
395 pub fn run_maintenance_checkpoint(&self) -> ApiResult<()> {
396 #[cfg(all(feature = "glean-sym", any(target_os = "android", target_os = "ios")))]
397 let timer_id = places_manager::run_maintenance_chk_pnt_time_temp.start();
398 let res = self.with_conn(storage::run_maintenance_checkpoint);
399
400 #[cfg(all(feature = "glean-sym", any(target_os = "android", target_os = "ios")))]
401 places_manager::run_maintenance_chk_pnt_time_temp.stop_and_accumulate(timer_id);
402
403 res
404 }
405
406 #[handle_error(crate::Error)]
407 pub fn query_autocomplete(&self, search: String, limit: i32) -> ApiResult<Vec<SearchResult>> {
408 self.with_conn(|conn| {
409 search_frecent(
410 conn,
411 SearchParams {
412 search_string: search,
413 limit: limit as u32,
414 },
415 )
416 .map(|search_results| search_results.into_iter().map(Into::into).collect())
417 })
418 }
419
420 #[handle_error(crate::Error)]
421 pub fn accept_result(&self, search_string: String, url: String) -> ApiResult<()> {
422 self.with_conn(|conn| {
423 match Url::parse(&url) {
424 Ok(url) => {
425 matcher::accept_result(conn, &search_string, &url)?;
426 }
427 Err(_) => {
428 warn!("Ignoring invalid URL in places_accept_result");
429 return Ok(());
430 }
431 };
432 Ok(())
433 })
434 }
435
436 #[handle_error(crate::Error)]
437 pub fn match_url(&self, query: String) -> ApiResult<Option<Url>> {
438 self.with_conn(|conn| matcher::match_url(conn, query))
439 }
440
441 #[handle_error(crate::Error)]
442 pub fn bookmarks_get_tree(&self, item_guid: &Guid) -> ApiResult<Option<BookmarkItem>> {
443 self.with_conn(|conn| bookmarks::fetch::fetch_tree(conn, item_guid))
444 }
445
446 #[handle_error(crate::Error)]
447 pub fn bookmarks_get_by_guid(
448 &self,
449 guid: &Guid,
450 get_direct_children: bool,
451 ) -> ApiResult<Option<BookmarkItem>> {
452 self.with_conn(|conn| {
453 let bookmark = bookmarks::fetch::fetch_bookmark(conn, guid, get_direct_children)?;
454 Ok(bookmark)
455 })
456 }
457
458 #[handle_error(crate::Error)]
459 pub fn bookmarks_get_all_with_url(&self, url: String) -> ApiResult<Vec<BookmarkItem>> {
460 self.with_conn(|conn| {
461 match Url::parse(&url) {
463 Ok(url) => Ok(bookmarks::fetch::fetch_bookmarks_by_url(conn, &url)?
464 .into_iter()
465 .map(|b| BookmarkItem::Bookmark { b })
466 .collect::<Vec<BookmarkItem>>()),
467 Err(e) => {
468 warn!("Invalid URL passed to bookmarks_get_all_with_url, {}", e);
470 Ok(Vec::<BookmarkItem>::new())
471 }
472 }
473 })
474 }
475
476 #[handle_error(crate::Error)]
477 pub fn bookmarks_search(&self, query: String, limit: i32) -> ApiResult<Vec<BookmarkItem>> {
478 self.with_conn(|conn| {
479 Ok(
481 bookmarks::fetch::search_bookmarks(conn, query.as_str(), limit as u32)?
482 .into_iter()
483 .map(|b| BookmarkItem::Bookmark { b })
484 .collect(),
485 )
486 })
487 }
488
489 #[handle_error(crate::Error)]
490 pub fn bookmarks_get_recent(&self, limit: i32) -> ApiResult<Vec<BookmarkItem>> {
491 self.with_conn(|conn| {
492 Ok(bookmarks::fetch::recent_bookmarks(conn, limit as u32)?
494 .into_iter()
495 .map(|b| BookmarkItem::Bookmark { b })
496 .collect())
497 })
498 }
499
500 #[handle_error(crate::Error)]
501 pub fn bookmarks_delete(&self, id: Guid) -> ApiResult<bool> {
502 self.with_conn(|conn| bookmarks::delete_bookmark(conn, &id))
503 }
504
505 #[handle_error(crate::Error)]
506 pub fn bookmarks_delete_everything(&self) -> ApiResult<()> {
507 self.with_conn(bookmarks::delete_everything)
508 }
509
510 #[handle_error(crate::Error)]
511 pub fn bookmarks_get_url_for_keyword(&self, keyword: String) -> ApiResult<Option<Url>> {
512 self.with_conn(|conn| bookmarks::bookmarks_get_url_for_keyword(conn, keyword.as_str()))
513 }
514
515 #[handle_error(crate::Error)]
516 pub fn bookmarks_insert(&self, data: InsertableBookmarkItem) -> ApiResult<Guid> {
517 self.with_conn(|conn| bookmarks::insert_bookmark(conn, data))
518 }
519
520 #[handle_error(crate::Error)]
521 pub fn bookmarks_update(&self, item: BookmarkUpdateInfo) -> ApiResult<()> {
522 self.with_conn(|conn| bookmarks::update_bookmark_from_info(conn, item))
523 }
524
525 #[handle_error(crate::Error)]
526 pub fn bookmarks_count_bookmarks_in_trees(&self, guids: &[Guid]) -> ApiResult<u32> {
527 self.with_conn(|conn| bookmarks::count_bookmarks_in_trees(conn, guids))
528 }
529
530 #[handle_error(crate::Error)]
531 pub fn places_history_import_from_ios(
532 &self,
533 db_path: String,
534 last_sync_timestamp: i64,
535 ) -> ApiResult<HistoryMigrationResult> {
536 self.with_conn(|conn| import_ios_history(conn, &db_path, last_sync_timestamp))
537 }
538}
539
540impl AsRef<SqlInterruptHandle> for PlacesConnection {
541 fn as_ref(&self) -> &SqlInterruptHandle {
542 &self.interrupt_handle
543 }
544}
545
546#[derive(Clone, PartialEq, Eq)]
547pub struct HistoryVisitInfo {
548 pub url: Url,
549 pub title: Option<String>,
550 pub timestamp: PlacesTimestamp,
551 pub visit_type: VisitType,
552 pub is_hidden: bool,
553 pub preview_image_url: Option<Url>,
554 pub is_remote: bool,
555}
556#[derive(Clone, PartialEq, Eq)]
557pub struct HistoryVisitInfosWithBound {
558 pub infos: Vec<HistoryVisitInfo>,
559 pub bound: i64,
560 pub offset: i64,
561}
562
563pub struct TopFrecentSiteInfo {
564 pub url: Url,
565 pub title: Option<String>,
566}
567
568pub enum FrecencyThresholdOption {
569 None,
570 SkipOneTimePages,
571}
572
573impl FrecencyThresholdOption {
574 fn value(&self) -> i64 {
575 match self {
576 FrecencyThresholdOption::None => 0,
577 FrecencyThresholdOption::SkipOneTimePages => SKIP_ONE_PAGE_FRECENCY_THRESHOLD,
578 }
579 }
580}
581
582pub struct SearchResult {
583 pub url: Url,
584 pub title: String,
585 pub frecency: i64,
586}
587
588pub struct Dummy {
590 pub md: Option<Vec<HistoryMetadata>>,
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596 use crate::test::new_mem_connection;
597
598 #[test]
599 fn test_accept_result_with_invalid_url() {
600 let conn = PlacesConnection::new(new_mem_connection());
601 let invalid_url = "http://1234.56.78.90".to_string();
602 assert!(PlacesConnection::accept_result(&conn, "ample".to_string(), invalid_url).is_ok());
603 }
604
605 #[test]
606 fn test_bookmarks_get_all_with_url_with_invalid_url() {
607 let conn = PlacesConnection::new(new_mem_connection());
608 let invalid_url = "http://1234.56.78.90".to_string();
609 assert!(PlacesConnection::bookmarks_get_all_with_url(&conn, invalid_url).is_ok());
610 }
611}