Skip to content

Commit

Permalink
bring back adding sites to keep favicon and pinned site dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarik Eshaq committed Nov 21, 2022
1 parent 56da0a1 commit c14743c
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 88 deletions.
159 changes: 107 additions & 52 deletions Storage/SQL/SQLiteHistory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import XCGLogger
import Glean

private let log = Logger.syncLogger
public let TopSiteCacheSize: Int32 = 16

open class IgnoredSiteError: MaybeErrorType {
open var description: String {
return "Ignored site."
}
}


private var ignoredSchemes = ["about"]

Expand Down Expand Up @@ -50,67 +56,114 @@ open class SQLiteHistory {
let database: BrowserDB
let favicons: SQLiteFavicons
let prefs: Prefs
let clearTopSitesQuery: (String, Args?) = ("DELETE FROM cached_top_sites", nil)
let notificationCenter: NotificationCenter

required public init(database: BrowserDB,
prefs: Prefs,
notificationCenter: NotificationCenter = NotificationCenter.default) {
self.database = database
self.favicons = SQLiteFavicons(db: self.database)
self.prefs = prefs
self.notificationCenter = notificationCenter

// We report the number of visits a user has
// this is helpful in determining what the size of users' history visits
// is like, to help guide testing the migration to the
// application-services implementation and testing the
// performance of the awesomebar.
self.countVisits { numVisits in
if let numVisits = numVisits {
GleanMetrics.History.numVisits.set(Int64(numVisits))
}
}
}

public func getSites(forURLs urls: [String]) -> Deferred<Maybe<Cursor<Site?>>> {
let inExpression = urls.joined(separator: "\",\"")
let sql = """
SELECT history.id AS historyID, history.url AS url, title, guid, iconID, iconURL, iconDate, iconType, iconWidth
FROM view_favicons_widest, history
WHERE history.id = siteID AND history.url IN (\"\(inExpression)\")
"""

let args: Args = []
return database.runQueryConcurrently(sql, args: args, factory: SQLiteHistory.iconHistoryColumnFactory)
}

public func countVisits(callback: @escaping (Int?) -> Void) {
let sql = "SELECT COUNT(*) FROM visits"
database.runQueryConcurrently(sql, args: nil, factory: SQLiteHistory.countAllVisitsFactory).uponQueue(.main) { result in
guard result.isSuccess else {
callback(nil)
return
}
// The result of a count query is only one row
if let res = result.successValue?.asArray().first {
if let res = res {
callback(res)
return
}
}
callback(nil)
}
}

func recordVisitedSite(_ site: Site) -> Success {
// Don't store visits to sites with about: protocols
if isIgnoredURL(site.url as String) {
return deferMaybe(IgnoredSiteError())
}


return database.withConnection { conn -> Void in
let now = Date.now()

if self.updateSite(site, atTime: now, withConnection: conn) > 0 {
return
}

// Insert instead.
if self.insertSite(site, atTime: now, withConnection: conn) > 0 {
return
}

let err = DatabaseError(description: "Unable to update or insert site; Invalid key returned")
log.error("recordVisitedSite encountered an error: \(err.localizedDescription)")
throw err
}
}

func updateSite(_ site: Site, atTime time: Timestamp, withConnection conn: SQLiteDBConnection) -> Int {
// We know we're adding a new visit, so we'll need to upload this record.
// If we ever switch to per-visit change flags, this should turn into a CASE statement like
// CASE WHEN title IS ? THEN max(should_upload, 1) ELSE should_upload END
// so that we don't flag this as changed unless the title changed.
//
// Note that we will never match against a deleted item, because deleted items have no URL,
// so we don't need to unset is_deleted here.
guard let host = (site.url as String).asURL?.normalizedHost else {
return 0
}

let update = "UPDATE history SET title = ?, local_modified = ?, should_upload = 1, domain_id = (SELECT id FROM domains where domain = ?) WHERE url = ?"
let updateArgs: Args? = [site.title, time, host, site.url]
if Logger.logPII {
log.debug("Setting title to \(site.title) for URL \(site.url)")
}
do {
try conn.executeChange(update, withArgs: updateArgs)
return conn.numberOfRowsModified
} catch let error as NSError {
log.warning("Update failed with error: \(error.localizedDescription)")
return 0
}
}

fileprivate func insertSite(_ site: Site, atTime time: Timestamp, withConnection conn: SQLiteDBConnection) -> Int {
if let host = (site.url as String).asURL?.normalizedHost {
do {
try conn.executeChange("INSERT OR IGNORE INTO domains (domain) VALUES (?)", withArgs: [host])
} catch let error as NSError {
log.warning("Domain insertion failed with \(error.localizedDescription)")
return 0
}

let insert = """
INSERT INTO history (
guid, url, title, local_modified, is_deleted, should_upload, domain_id
)
SELECT ?, ?, ?, ?, 0, 1, id FROM domains WHERE domain = ?
"""

let insertArgs: Args? = [site.guid ?? Bytes.generateGUID(), site.url, site.title, time, host]
do {
try conn.executeChange(insert, withArgs: insertArgs)
} catch let error as NSError {
log.warning("Site insertion failed with \(error.localizedDescription)")
return 0
}

return 1
}

if Logger.logPII {
log.warning("Invalid URL \(site.url). Not stored in history.")
}
return 0
}
}

private let topSitesQuery = """
SELECT cached_top_sites.*, page_metadata.provider_name \
FROM cached_top_sites \
LEFT OUTER JOIN page_metadata ON cached_top_sites.url = page_metadata.site_url \
ORDER BY frecencies DESC LIMIT (?)
"""


extension SQLiteHistory: PinnedSites {
public func removeFromPinnedTopSites(_ site: Site) -> Success {
guard let host = (site.url as String).asURL?.normalizedHost else {
Expand Down Expand Up @@ -145,18 +198,20 @@ extension SQLiteHistory: PinnedSites {
}

public func addPinnedTopSite(_ site: Site) -> Success { // needs test
let now = Date.now()
guard let host = (site.url as String).asURL?.normalizedHost else {
return deferMaybe(DatabaseError(description: "Invalid site \(site.url)"))
}
return self.recordVisitedSite(site) >>== {
let now = Date.now()
guard let host = (site.url as String).asURL?.normalizedHost else {
return deferMaybe(DatabaseError(description: "Invalid site \(site.url)"))
}

let args: Args = [site.url, now, site.title, site.id, host]
let arglist = BrowserDB.varlist(args.count)
let args: Args = [site.url, now, site.title, site.id, host]
let arglist = BrowserDB.varlist(args.count)

return self.database.run([("INSERT OR REPLACE INTO pinned_top_sites (url, pinDate, title, historyID, domain) VALUES \(arglist)", args)])
>>== {
self.notificationCenter.post(name: .TopSitesUpdated, object: self)
return Success()
return self.database.run([("INSERT OR REPLACE INTO pinned_top_sites (url, pinDate, title, historyID, domain) VALUES \(arglist)", args)])
>>== {
self.notificationCenter.post(name: .TopSitesUpdated, object: self)
return Success()
}
}
}

Expand Down
73 changes: 37 additions & 36 deletions Storage/SQL/SQLiteHistoryFavicons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,50 +100,51 @@ extension SQLiteHistory: Favicons {
* in the history table.
*/
public func addFavicon(_ icon: Favicon, forSite site: Site) -> Deferred<Maybe<Int>> {
func doChange(_ query: String, args: Args?) -> Deferred<Maybe<Int>> {
return database.withConnection { conn -> Int in
// Blind! We don't see failure here.
let id = self.favicons.insertOrUpdateFaviconInTransaction(icon, conn: conn)

// Now set up the mapping.
try conn.executeChange(query, withArgs: args)

guard let faviconID = id else {
let err = DatabaseError(description: "Error adding favicon. ID = 0")
log.error("addFavicon(_:, forSite:) encountered an error: \(err.localizedDescription)")
throw err
return self.recordVisitedSite(site) >>== {
func doChange(_ query: String, args: Args?) -> Deferred<Maybe<Int>> {
return self.database.withConnection { conn -> Int in
// Blind! We don't see failure here.
let id = self.favicons.insertOrUpdateFaviconInTransaction(icon, conn: conn)

// Now set up the mapping.
try conn.executeChange(query, withArgs: args)

guard let faviconID = id else {
let err = DatabaseError(description: "Error adding favicon. ID = 0")
log.error("addFavicon(_:, forSite:) encountered an error: \(err.localizedDescription)")
throw err
}

return faviconID
}
}
let siteSubselect = "(SELECT id FROM history WHERE url = ?)"
let iconSubselect = "(SELECT id FROM favicons WHERE url = ?)"
let insertOrIgnore = "INSERT OR IGNORE INTO favicon_sites (siteID, faviconID) VALUES "
if let iconID = icon.id {
// Easy!
if let siteID = site.id {
// So easy!
let args: Args? = [siteID, iconID]
return doChange("\(insertOrIgnore) (?, ?)", args: args)
}

// Nearly easy.
let args: Args? = [site.url, iconID]
return doChange("\(insertOrIgnore) (\(siteSubselect), ?)", args: args)

return faviconID
}
}

let siteSubselect = "(SELECT id FROM history WHERE url = ?)"
let iconSubselect = "(SELECT id FROM favicons WHERE url = ?)"
let insertOrIgnore = "INSERT OR IGNORE INTO favicon_sites (siteID, faviconID) VALUES "
if let iconID = icon.id {
// Easy!
// Sigh.
if let siteID = site.id {
// So easy!
let args: Args? = [siteID, iconID]
return doChange("\(insertOrIgnore) (?, ?)", args: args)
let args: Args? = [siteID, icon.url]
return doChange("\(insertOrIgnore) (?, \(iconSubselect))", args: args)
}

// Nearly easy.
let args: Args? = [site.url, iconID]
return doChange("\(insertOrIgnore) (\(siteSubselect), ?)", args: args)

// The worst.
let args: Args? = [site.url, icon.url]
return doChange("\(insertOrIgnore) (\(siteSubselect), \(iconSubselect))", args: args)
}

// Sigh.
if let siteID = site.id {
let args: Args? = [siteID, icon.url]
return doChange("\(insertOrIgnore) (?, \(iconSubselect))", args: args)
}

// The worst.
let args: Args? = [site.url, icon.url]
return doChange("\(insertOrIgnore) (\(siteSubselect), \(iconSubselect))", args: args)
}

public func getFaviconImage(forSite site: Site) -> Deferred<Maybe<UIImage>> {
Expand Down

0 comments on commit c14743c

Please sign in to comment.