Revision control

Copy as Markdown

/* 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/. */
import Foundation
import os.log
typealias UniffiPlacesApi = PlacesApi
typealias UniffiPlacesConnection = PlacesConnection
/**
* This is specifically for throwing when there is
* API misuse and/or connection issues with PlacesReadConnection
*/
public enum PlacesConnectionError: Error {
case connUseAfterApiClosed
}
/**
* This is something like a places connection manager. It primarialy exists to
* ensure that only a single write connection is active at once.
*
* If it helps, you can think of this as something like a connection pool
* (although it does not actually perform any pooling).
*/
public class PlacesAPI {
private let writeConn: PlacesWriteConnection
private let api: UniffiPlacesApi
private let queue = DispatchQueue(label: "com.mozilla.places.api")
/**
* Initialize a PlacesAPI
*
* - Parameter path: an absolute path to a file that will be used for the internal database.
*
* - Throws: `PlacesApiError` if initializing the database failed.
*/
public init(path: String) throws {
try api = placesApiNew(dbPath: path)
let uniffiConn = try api.newConnection(connType: ConnectionType.readWrite)
writeConn = try PlacesWriteConnection(conn: uniffiConn)
writeConn.api = self
}
/**
* Open a new reader connection.
*
* - Throws: `PlacesApiError` if a connection could not be opened.
*/
open func openReader() throws -> PlacesReadConnection {
return try queue.sync {
let uniffiConn = try api.newConnection(connType: ConnectionType.readOnly)
return try PlacesReadConnection(conn: uniffiConn, api: self)
}
}
/**
* Get the writer connection.
*
* - Note: There is only ever a single writer connection,
* and it's opened when the database is constructed,
* so this function does not throw
*/
open func getWriter() -> PlacesWriteConnection {
return queue.sync {
self.writeConn
}
}
open func registerWithSyncManager() {
queue.sync {
self.api.registerWithSyncManager()
}
}
}
/**
* A read-only connection to the places database.
*/
public class PlacesReadConnection {
fileprivate let queue = DispatchQueue(label: "com.mozilla.places.conn")
fileprivate var conn: UniffiPlacesConnection
fileprivate weak var api: PlacesAPI?
private let interruptHandle: SqlInterruptHandle
fileprivate init(conn: UniffiPlacesConnection, api: PlacesAPI? = nil) throws {
self.conn = conn
self.api = api
interruptHandle = self.conn.newInterruptHandle()
}
// Note: caller synchronizes!
fileprivate func checkApi() throws {
if api == nil {
throw PlacesConnectionError.connUseAfterApiClosed
}
}
/**
* Returns the bookmark subtree rooted at `rootGUID`.
*
* This differs from `getBookmark` in that it populates folder children
* recursively (specifically, any `BookmarkFolder`s in the returned value
* will have their `children` list populated, and not just `childGUIDs`.
*
* However, if `recursive: false` is passed, only a single level of child
* nodes are returned for folders.
*
* - Parameter rootGUID: the GUID where to start the tree.
*
* - Parameter recursive: Whether or not to return more than a single
* level of children for folders. If false, then
* any folders which are children of the requested
* node will *only* have their `childGUIDs`
* populated, and *not* their `children`.
*
* - Returns: The bookmarks tree starting from `rootGUID`, or null if the
* provided guid didn't refer to a known bookmark item.
* - Throws:
* - `PlacesApiError.databaseCorrupt`: If corruption is encountered when fetching
* the tree
* - `PlacesApiError.databaseInterrupted`: If a call is made to `interrupt()` on this
* object from another thread.
* - `PlacesConnectionError.connUseAfterAPIClosed`: If the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.databaseBusy`: If this query times out with a SQLITE_BUSY error.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*/
open func getBookmarksTree(rootGUID: Guid, recursive: Bool) throws -> BookmarkNodeData? {
return try queue.sync {
try self.checkApi()
if recursive {
return try self.conn.bookmarksGetTree(itemGuid: rootGUID)?.asBookmarkNodeData
} else {
return try self.conn.bookmarksGetByGuid(guid: rootGUID, getDirectChildren: true)?.asBookmarkNodeData
}
}
}
/**
* Returns the information about the bookmark with the provided id.
*
* This differs from `getBookmarksTree` in that it does not populate the `children` list
* if `guid` refers to a folder (However, its `childGUIDs` list will be
* populated).
*
* - Parameter guid: the guid of the bookmark to fetch.
*
* - Returns: The bookmark node, or null if the provided guid didn't refer to a
* known bookmark item.
* - Throws:
* - `PlacesApiError.databaseInterrupted`: If a call is made to `interrupt()` on this
* object from another thread.
* - `PlacesConnectionError.connUseAfterAPIClosed`: If the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.databaseBusy`: If this query times out with a SQLITE_BUSY error.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*/
open func getBookmark(guid: Guid) throws -> BookmarkNodeData? {
return try queue.sync {
try self.checkApi()
return try self.conn.bookmarksGetByGuid(guid: guid, getDirectChildren: false)?.asBookmarkNodeData
}
}
/**
* Returns the list of bookmarks with the provided URL.
*
* - Note: If the URL is not percent-encoded/punycoded, that will be performed
* internally, and so the returned bookmarks may not have an identical
* URL to the one passed in, however, it will be the same according to
*
* - Parameter url: The url to search for.
*
* - Returns: A list of bookmarks that have the requested URL.
*
* - Throws:
* - `PlacesApiError.databaseInterrupted`: If a call is made to `interrupt()` on this
* object from another thread.
* - `PlacesConnectionError.connUseAfterAPIClosed`: If the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.databaseBusy`: If this query times out with a SQLITE_BUSY error.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*/
open func getBookmarksWithURL(url: Url) throws -> [BookmarkItemData] {
return try queue.sync {
try self.checkApi()
let items = try self.conn.bookmarksGetAllWithUrl(url: url)
return toBookmarkItemDataList(items: items)
}
}
/**
* Returns the URL for the provided search keyword, if one exists.
*
* - Parameter keyword: The search keyword.
* - Returns: The bookmarked URL for the keyword, if set.
* - Throws:
* - `PlacesApiError.databaseInterrupted`: If a call is made to `interrupt()` on this
* object from another thread.
* - `PlacesConnectionError.connUseAfterAPIClosed`: If the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.databaseBusy`: If this query times out with a SQLITE_BUSY error.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*/
open func getBookmarkURLForKeyword(keyword: String) throws -> Url? {
return try queue.sync {
try self.checkApi()
return try self.conn.bookmarksGetUrlForKeyword(keyword: keyword)
}
}
/**
* Returns the list of bookmarks that match the provided search string.
*
* The order of the results is unspecified.
*
* - Parameter query: The search query
* - Parameter limit: The maximum number of items to return.
* - Returns: A list of bookmarks where either the URL or the title
* contain a word (e.g. space separated item) from the
* query.
* - Throws:
* - `PlacesApiError.databaseInterrupted`: If a call is made to `interrupt()` on this
* object from another thread.
* - `PlacesConnectionError.connUseAfterAPIClosed`: If the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.databaseBusy`: If this query times out with a SQLITE_BUSY error.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*/
open func searchBookmarks(query: String, limit: UInt) throws -> [BookmarkItemData] {
return try queue.sync {
try self.checkApi()
let items = try self.conn.bookmarksSearch(query: query, limit: Int32(limit))
return toBookmarkItemDataList(items: items)
}
}
/**
* Returns the list of most recently added bookmarks.
*
* The result list be in order of time of addition, descending (more recent
* additions first), and will contain no folder or separator nodes.
*
* - Parameter limit: The maximum number of items to return.
* - Returns: A list of recently added bookmarks.
* - Throws:
* - `PlacesApiError.databaseInterrupted`: If a call is made to
* `interrupt()` on this object
* from another thread.
* - `PlacesConnectionError.connUseAfterAPIClosed`: If the PlacesAPI that returned
* this connection object has
* been closed. This indicates
* API misuse.
* - `PlacesApiError.databaseBusy`: If this query times out with a
* SQLITE_BUSY error.
* - `PlacesApiError.unexpected`: When an error that has not specifically
* been exposed to Swift is encountered (for
* example IO errors from the database code,
* etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us
* know).
*/
open func getRecentBookmarks(limit: UInt) throws -> [BookmarkItemData] {
return try queue.sync {
try self.checkApi()
let items = try self.conn.bookmarksGetRecent(limit: Int32(limit))
return toBookmarkItemDataList(items: items)
}
}
open func getLatestHistoryMetadataForUrl(url: Url) throws -> HistoryMetadata? {
return try queue.sync {
try self.checkApi()
return try self.conn.getLatestHistoryMetadataForUrl(url: url)
}
}
open func getHistoryMetadataSince(since: Int64) throws -> [HistoryMetadata] {
return try queue.sync {
try self.checkApi()
return try self.conn.getHistoryMetadataSince(since: since)
}
}
open func getHistoryMetadataBetween(start: Int64, end: Int64) throws -> [HistoryMetadata] {
return try queue.sync {
try self.checkApi()
return try self.conn.getHistoryMetadataBetween(start: start, end: end)
}
}
open func getHighlights(weights: HistoryHighlightWeights, limit: Int32) throws -> [HistoryHighlight] {
return try queue.sync {
try self.checkApi()
return try self.conn.getHistoryHighlights(weights: weights, limit: limit)
}
}
open func queryHistoryMetadata(query: String, limit: Int32) throws -> [HistoryMetadata] {
return try queue.sync {
try self.checkApi()
return try self.conn.queryHistoryMetadata(query: query, limit: limit)
}
}
// MARK: History Read APIs
open func matchUrl(query: String) throws -> Url? {
return try queue.sync {
try self.checkApi()
return try self.conn.matchUrl(query: query)
}
}
open func queryAutocomplete(search: String, limit: Int32) throws -> [SearchResult] {
return try queue.sync {
try self.checkApi()
return try self.conn.queryAutocomplete(search: search, limit: limit)
}
}
open func getVisitUrlsInRange(start: PlacesTimestamp, end: PlacesTimestamp, includeRemote: Bool)
throws -> [Url]
{
return try queue.sync {
try self.checkApi()
return try self.conn.getVisitedUrlsInRange(start: start, end: end, includeRemote: includeRemote)
}
}
open func getVisitInfos(start: PlacesTimestamp, end: PlacesTimestamp, excludeTypes: VisitTransitionSet)
throws -> [HistoryVisitInfo]
{
return try queue.sync {
try self.checkApi()
return try self.conn.getVisitInfos(startDate: start, endDate: end, excludeTypes: excludeTypes)
}
}
open func getVisitCount(excludedTypes: VisitTransitionSet) throws -> Int64 {
return try queue.sync {
try self.checkApi()
return try self.conn.getVisitCount(excludeTypes: excludedTypes)
}
}
open func getVisitPageWithBound(
bound: Int64,
offset: Int64,
count: Int64,
excludedTypes: VisitTransitionSet
)
throws -> HistoryVisitInfosWithBound
{
return try queue.sync {
try self.checkApi()
return try self.conn.getVisitPageWithBound(
bound: bound, offset: offset, count: count, excludeTypes: excludedTypes
)
}
}
open func getVisited(urls: [String]) throws -> [Bool] {
return try queue.sync {
try self.checkApi()
return try self.conn.getVisited(urls: urls)
}
}
open func getTopFrecentSiteInfos(numItems: Int32, thresholdOption: FrecencyThresholdOption)
throws -> [TopFrecentSiteInfo]
{
return try queue.sync {
try self.checkApi()
return try self.conn.getTopFrecentSiteInfos(
numItems: numItems,
thresholdOption: thresholdOption
)
}
}
/**
* Attempt to interrupt a long-running operation which may be
* happening concurrently. If the operation is interrupted,
* it will fail.
*
* - Note: Not all operations can be interrupted, and no guarantee is
* made that a concurrent interrupt call will be respected
* (as we may miss it).
*/
open func interrupt() {
interruptHandle.interrupt()
}
}
/**
* A read-write connection to the places database.
*/
public class PlacesWriteConnection: PlacesReadConnection {
/**
* Run periodic database maintenance. This might include, but is
* not limited to:
*
* - `VACUUM`ing.
* - Requesting that the indices in our tables be optimized.
* - Periodic repair or deletion of corrupted records.
* - Deleting older visits when the database exceeds dbSizeLimit
* - etc.
*
* Maintenance in performed in small chunks at a time to avoid blocking the
* DB connection for too long. This means that this should be called
* regularly when the app is idle.
*
* - Parameter dbSizeLimit: Maximum DB size to aim for, in bytes. If the
* database exceeds this size, we will prune a small number of visits. For
* reference, desktop normally uses 75 MiB (78643200). If it determines
* that either the disk or memory is constrained then it halves the amount.
* The default of 0 disables pruning.
*
* - Throws:
* - `PlacesConnectionError.connUseAfterAPIClosed`: if the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*
*/
open func runMaintenance(dbSizeLimit: UInt32 = 0) throws {
return try queue.sync {
try self.checkApi()
// The Kotlin code uses a higher pruneLimit, while Swift is extra conservative. The
// main reason for this is the v119 places incident. Once we figure that one out more,
// let's increase the prune limit here as well.
_ = try self.conn.runMaintenancePrune(dbSizeLimit: dbSizeLimit, pruneLimit: 6)
try self.conn.runMaintenanceVacuum()
try self.conn.runMaintenanceOptimize()
try self.conn.runMaintenanceCheckpoint()
}
}
/**
* Delete the bookmark with the provided GUID.
*
* If the requested bookmark is a folder, all children of
* bookmark are deleted as well, recursively.
*
* - Parameter guid: The GUID of the bookmark to delete
*
* - Returns: Whether or not the bookmark existed.
*
* - Throws:
* - `PlacesApiError.cannotUpdateRoot`: if `guid` is one of the bookmark roots.
* - `PlacesConnectionError.connUseAfterAPIClosed`: if the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*/
@discardableResult
open func deleteBookmarkNode(guid: Guid) throws -> Bool {
return try queue.sync {
try self.checkApi()
return try self.conn.bookmarksDelete(id: guid)
}
}
/**
* Create a bookmark folder, returning its guid.
*
* - Parameter parentGUID: The GUID of the (soon to be) parent of this bookmark.
*
* - Parameter title: The title of the folder.
*
* - Parameter position: The index where to insert the record inside
* its parent. If not provided, this item will
* be appended.
*
* - Returns: The GUID of the newly inserted bookmark folder.
*
* - Throws:
* - `PlacesApiError.cannotUpdateRoot`: If `parentGUID` is `BookmarkRoots.RootGUID`.
* - `PlacesApiError.noSuchItem`: If `parentGUID` does not refer to a known bookmark.
* - `PlacesApiError.invalidParent`: If `parentGUID` refers to a bookmark which is
* not a folder.
* - `PlacesConnectionError.connUseAfterAPIClosed`: if the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*/
@discardableResult
open func createFolder(parentGUID: Guid,
title: String,
position: UInt32? = nil) throws -> Guid
{
return try queue.sync {
try self.checkApi()
let p = position == nil ? BookmarkPosition.append : BookmarkPosition.specific(pos: position ?? 0)
let f = InsertableBookmarkFolder(parentGuid: parentGUID, position: p, title: title, children: [])
return try doInsert(item: InsertableBookmarkItem.folder(f: f))
}
}
/**
* Create a bookmark separator, returning its guid.
*
* - Parameter parentGUID: The GUID of the (soon to be) parent of this bookmark.
*
* - Parameter position: The index where to insert the record inside
* its parent. If not provided, this item will
* be appended.
*
* - Returns: The GUID of the newly inserted bookmark separator.
* - Throws:
* - `PlacesApiError.cannotUpdateRoot`: If `parentGUID` is `BookmarkRoots.RootGUID`.
* - `PlacesApiError.noSuchItem`: If `parentGUID` does not refer to a known bookmark.
* - `PlacesApiError.invalidParent`: If `parentGUID` refers to a bookmark which is
* not a folder.
* - `PlacesConnectionError.connUseAfterAPIClosed`: if the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*/
@discardableResult
open func createSeparator(parentGUID: Guid, position: UInt32? = nil) throws -> Guid {
return try queue.sync {
try self.checkApi()
let p = position == nil ? BookmarkPosition.append : BookmarkPosition.specific(pos: position ?? 0)
let s = InsertableBookmarkSeparator(parentGuid: parentGUID, position: p)
return try doInsert(item: InsertableBookmarkItem.separator(s: s))
}
}
/**
* Create a bookmark item, returning its guid.
*
* - Parameter parentGUID: The GUID of the (soon to be) parent of this bookmark.
*
* - Parameter position: The index where to insert the record inside
* its parent. If not provided, this item will
* be appended.
*
* - Parameter url: The URL to bookmark
*
* - Parameter title: The title of the new bookmark, if any.
*
* - Returns: The GUID of the newly inserted bookmark item.
*
* - Throws:
* - `PlacesApiError.urlParseError`: If `url` is not a valid URL.
* - `PlacesApiError.urlTooLong`: If `url` is more than 65536 bytes after
* punycoding and hex encoding.
* - `PlacesApiError.cannotUpdateRoot`: If `parentGUID` is `BookmarkRoots.RootGUID`.
* - `PlacesApiError.noSuchItem`: If `parentGUID` does not refer to a known bookmark.
* - `PlacesApiError.invalidParent`: If `parentGUID` refers to a bookmark which is
* not a folder.
* - `PlacesConnectionError.connUseAfterAPIClosed`: if the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*/
@discardableResult
open func createBookmark(parentGUID: String,
url: String,
title: String?,
position: UInt32? = nil) throws -> Guid
{
return try queue.sync {
try self.checkApi()
let p = position == nil ? BookmarkPosition.append : BookmarkPosition.specific(pos: position ?? 0)
let bm = InsertableBookmark(parentGuid: parentGUID, position: p, url: url, title: title)
return try doInsert(item: InsertableBookmarkItem.bookmark(b: bm))
}
}
/**
* Update a bookmark to the provided info.
*
* - Parameters:
* - guid: Guid of the bookmark to update
*
* - parentGUID: If the record should be moved to another folder, the guid
* of the folder it should be moved to. Interacts with
* `position`, see the note below for details.
*
* - position: If the record should be moved, the 0-based index where it
* should be moved to. Interacts with `parentGUID`, see the note
* below for details
*
* - title: If the record is a `BookmarkNodeType.bookmark` or a `BookmarkNodeType.folder`,
* and its title should be changed, then the new value of the title.
*
* - url: If the record is a `BookmarkNodeType.bookmark` node, and its `url`
* should be changed, then the new value for the url.
*
* - Note: The `parentGUID` and `position` parameters interact with eachother
* as follows:
*
* - If `parentGUID` is not provided and `position` is, we treat this
* a move within the same folder.
*
* - If `parentGUID` and `position` are both provided, we treat this as
* a move to / within that folder, and we insert at the requested
* position.
*
* - If `position` is not provided (and `parentGUID` is) then its
* treated as a move to the end of that folder.
* - Throws:
* - `PlacesApiError.illegalChange`: If the change requested is impossible given the
* type of the item in the DB. For example, on
* attempts to update the title of a separator.
* - `PlacesApiError.cannotUpdateRoot`: If `guid` is a member of `BookmarkRoots.All`, or
* `parentGUID` is is `BookmarkRoots.RootGUID`.
* - `PlacesApiError.noSuchItem`: If `guid` or `parentGUID` (if specified) do not refer
* to known bookmarks.
* - `PlacesApiError.invalidParent`: If `parentGUID` is specified and refers to a bookmark
* which is not a folder.
* - `PlacesConnectionError.connUseAfterAPIClosed`: if the PlacesAPI that returned this connection
* object has been closed. This indicates API
* misuse.
* - `PlacesApiError.unexpected`: When an error that has not specifically been exposed
* to Swift is encountered (for example IO errors from
* the database code, etc).
* - `PlacesApiError.panic`: If the rust code panics while completing this
* operation. (If this occurs, please let us know).
*/
open func updateBookmarkNode(guid: Guid,
parentGUID: Guid? = nil,
position: UInt32? = nil,
title: String? = nil,
url: Url? = nil) throws
{
try queue.sync {
try self.checkApi()
let data = BookmarkUpdateInfo(
guid: guid,
title: title,
url: url,
parentGuid: parentGUID,
position: position
)
try self.conn.bookmarksUpdate(data: data)
}
}
// Helper for the various creation functions.
// Note: Caller synchronizes
private func doInsert(item: InsertableBookmarkItem) throws -> Guid {
return try conn.bookmarksInsert(bookmark: item)
}
// MARK: History metadata write APIs
open func noteHistoryMetadataObservation(
observation: HistoryMetadataObservation
) throws {
try queue.sync {
try self.checkApi()
try self.conn.noteHistoryMetadataObservation(data: observation)
}
}
// Keeping these three functions inline with what Kotlin (PlacesConnection.kt)
// to make future work more symmetrical
open func noteHistoryMetadataObservationViewTime(key: HistoryMetadataKey, viewTime: Int32?) throws {
let obs = HistoryMetadataObservation(
url: key.url,
referrerUrl: key.referrerUrl,
searchTerm: key.searchTerm,
viewTime: viewTime
)
try noteHistoryMetadataObservation(observation: obs)
}
open func noteHistoryMetadataObservationDocumentType(key: HistoryMetadataKey, documentType: DocumentType) throws {
let obs = HistoryMetadataObservation(
url: key.url,
referrerUrl: key.referrerUrl,
searchTerm: key.searchTerm,
documentType: documentType
)
try noteHistoryMetadataObservation(observation: obs)
}
open func noteHistoryMetadataObservationTitle(key: HistoryMetadataKey, title: String) throws {
let obs = HistoryMetadataObservation(
url: key.url,
referrerUrl: key.referrerUrl,
searchTerm: key.searchTerm,
title: title
)
try noteHistoryMetadataObservation(observation: obs)
}
open func deleteHistoryMetadataOlderThan(olderThan: Int64) throws {
try queue.sync {
try self.checkApi()
try self.conn.metadataDeleteOlderThan(olderThan: olderThan)
}
}
open func deleteHistoryMetadata(key: HistoryMetadataKey) throws {
try queue.sync {
try self.checkApi()
try self.conn.metadataDelete(
url: key.url,
referrerUrl: key.referrerUrl,
searchTerm: key.searchTerm
)
}
}
// MARK: History Write APIs
open func deleteVisitsFor(url: Url) throws {
try queue.sync {
try self.checkApi()
try self.conn.deleteVisitsFor(url: url)
}
}
open func deleteVisitsBetween(start: PlacesTimestamp, end: PlacesTimestamp) throws {
try queue.sync {
try self.checkApi()
try self.conn.deleteVisitsBetween(start: start, end: end)
}
}
open func deleteVisit(url: Url, timestamp: PlacesTimestamp) throws {
try queue.sync {
try self.checkApi()
try self.conn.deleteVisit(url: url, timestamp: timestamp)
}
}
open func deleteEverythingHistory() throws {
try queue.sync {
try self.checkApi()
try self.conn.deleteEverythingHistory()
}
}
open func acceptResult(searchString: String, url: String) throws {
return try queue.sync {
try self.checkApi()
return try self.conn.acceptResult(searchString: searchString, url: url)
}
}
open func applyObservation(visitObservation: VisitObservation) throws {
return try queue.sync {
try self.checkApi()
return try self.conn.applyObservation(visit: visitObservation)
}
}
open func migrateHistoryFromBrowserDb(path: String, lastSyncTimestamp: Int64) throws -> HistoryMigrationResult {
return try queue.sync {
try self.checkApi()
return try self.conn.placesHistoryImportFromIos(dbPath: path, lastSyncTimestamp: lastSyncTimestamp)
}
}
}