/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::apply_observation; use crate::db::PlacesDb; use crate::error::*; use crate::observation::VisitObservation; use crate::types::*; use types::Timestamp; use url::Url; // This module can become, roughly: PlacesUtils.history() pub fn can_add_url(_url: &Url) -> Result { Ok(true) } // eg: PlacesUtils.history.insert({url: "http", title: ..., visits: [{date: ...}]}) // Structs representing place and visit infos for this API. // (Not clear this makes sense - it's a copy of what desktop does just to // get started) // NOTE THAT THESE STRUCTS are only for demo purposes, showing how // PlacesUtils.history.insert() could be implemented using the same shaped // objects. // They should really be moved into an "examples" folder. #[derive(Debug)] pub struct AddablePlaceInfo { pub url: Url, pub title: Option, pub visits: Vec, } #[derive(Debug)] pub struct AddableVisit { pub date: Timestamp, pub transition: VisitType, pub referrer: Option, pub is_local: bool, } // insert a visit a'la PlacesUtils.history.insert() pub fn insert(conn: &mut PlacesDb, place: AddablePlaceInfo) -> Result<()> { for v in place.visits { let obs = VisitObservation::new(place.url.clone()) .with_visit_type(v.transition) .with_at(v.date) .with_title(place.title.clone()) .with_is_remote(!v.is_local); // .with_referrer(...) ???? //if place.referrer apply_observation(conn, obs)?; } Ok(()) } #[cfg(test)] mod tests { use super::*; use crate::api::places_api::test::new_mem_connection; #[test] fn test_insert() { let mut c = new_mem_connection(); let url = Url::parse("http://example.com").expect("it's a valid url"); let date = Timestamp::now(); let visits = vec![AddableVisit { date, transition: VisitType::Link, referrer: None, is_local: true, }]; let a = AddablePlaceInfo { url, title: None, visits, }; insert(&mut c, a).expect("should insert"); // For now, a raw read of the DB. let sql = "SELECT p.id, p.url, p.title, p.visit_count_local, p.visit_count_remote, p.hidden, p.typed, p.frecency, p.last_visit_date_local, p.last_visit_date_remote, p.guid, p.foreign_count, p.url_hash, p.description, p.preview_image_url, p.origin_id, v.is_local, v.from_visit, v.place_id, v.visit_date, v.visit_type FROM moz_places p, moz_historyvisits v WHERE v.place_id = p.id"; let mut stmt = c.db.prepare(sql).expect("valid sql"); let mut rows = stmt.query([]).expect("should execute"); let result = rows.next().expect("should get a row"); let row = result.expect("expect anything"); assert_eq!( row.get::<_, String>("url").expect("should work"), "http://example.com/" ); // hrmph - note trailing slash assert_eq!( row.get::<_, Timestamp>("visit_date").expect("should work"), date ); assert_ne!(row.get::<_, i32>("frecency").expect("should work"), 0); // XXX - check more. } } ///////////////////////////////////////////// // Stuff to reimplement nsHistory::VisitUri() fn is_recently_visited(_url: &Url) -> bool { // History.cpp keeps an in-memory hashtable of urls visited in the last // 6 minutes to avoid pages which self-refresh from getting many entries. // ie, there's no DB query done here. // TODO: implement this. false } fn add_recently_visited(_url: &Url) {} // Other "recent" flags: // enum RecentEventFlags { // RECENT_TYPED = 1 << 0, // User typed in URL recently // RECENT_ACTIVATED = 1 << 1, // User tapped URL link recently // RECENT_BOOKMARKED = 1 << 2 // User bookmarked URL recently // }; // All of which are just a 15-second in-memory cache, and all of which appear // to rely on explicit calls to set the flag. eg: // nsNavHistory::MarkPageAsTyped(nsIURI *aURI) just adds to the cache. // Is this URL the *source* is a redirect? Note that this is different than // the redirect flags in the TransitionType, as that is the flag for the // *target* of the redirect. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)] pub enum RedirectSourceType { Temporary, Permanent, } // nsIHistory::VisitURI - this is the main interface used by the browser // itself to record visits. // This differs from the desktop implementation in one major way - instead // of using various browser-specific heuristics to compute the VisitType // we assume the caller has already done this and passed the correct transition // flags in. pub fn visit_uri( conn: &mut PlacesDb, url: &Url, last_url: Option, // To be more honest, this would *not* take a VisitType, // but instead other "internal" nsIHistory flags, from which // it would deduce the VisitType. transition: VisitType, redirect_source: Option, is_error_page: bool, ) -> Result<()> { // Silently return if URI is something we shouldn't add to DB. if !can_add_url(url)? { return Ok(()); }; // Do not save a reloaded uri if we have visited the same URI recently. // (Note that desktop implies `reload` based of the "is it the same as last // and is it recent" check below) - but here we are asking for the // VisitType to be passed in, which explicitly has a value for reload. // Note clear if we should try and unify these. // (and note that if we can, we can drop the recently_visited cache) if let Some(ref last) = last_url { if url == last && is_recently_visited(url) { // it's a reload we don't want to record, although we do want to // update it as being recent. add_recently_visited(url); return Ok(()); }; } // So add it. // XXX - translate the flags passed to this function, along with the // RECENT_* cache above to create the correct Transition type. // call get_hidden_state to see if .hidden should be set. // get_hidden_state... // EMBED visits are session-persistent and should not go through the database. // They exist only to keep track of isVisited status during the session. if transition == VisitType::Embed { warn!("Embed visit, but in-memory storage of these isn't done yet"); return Ok(()); } let obs = VisitObservation::new(url.clone()) .with_is_error(is_error_page) .with_visit_type(transition) .with_is_redirect_source(redirect_source.map(|_r| true)) .with_is_permanent_redirect_source( redirect_source.map(|r| r == RedirectSourceType::Permanent), ); apply_observation(conn, obs) }