Dactyloidae/mobile/ios/Storage/SQL/SQLiteBookmarksResetting.swift

146 lines
5.9 KiB
Swift
Raw Normal View History

2026-06-26 21:04:09 -07:00
/* 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 Shared
import XCGLogger
private let log = Logger.syncLogger
extension MergedSQLiteBookmarks: AccountRemovalDelegate {
public func onRemovedAccount() -> Success {
return self.local.onRemovedAccount() >>> self.buffer.onRemovedAccount
}
}
extension MergedSQLiteBookmarks: ResettableSyncStorage {
public func resetClient() -> Success {
return self.local.resetClient() >>> self.buffer.resetClient
}
}
extension SQLiteBookmarkBufferStorage: AccountRemovalDelegate {
public func onRemovedAccount() -> Success {
return self.resetClient()
}
}
extension SQLiteBookmarkBufferStorage: ResettableSyncStorage {
/**
* Our buffer is simply a copy of server contents. That means we should
* be very willing to drop it and re-populate it from the server whenever we might
* be out of sync. See Bug 1212431 Comment 2.
*/
public func resetClient() -> Success {
return self.wipeBookmarks()
}
public func wipeBookmarks() -> Success {
return self.db.run([
"DELETE FROM \(TableBookmarksBufferStructure)",
"DELETE FROM \(TableBookmarksBuffer)",
])
}
}
extension SQLiteBookmarks {
/**
* If a synced record is deleted locally, but hasn't been synced to the server,
* then `preserveDeletions=true` will result in that deletion being kept.
*
* During a reset, we'll redownload all server records. If we don't keep the
* local deletion, then when we re-process the (non-deleted) server counterpart
* to the now-missing local record, it'll be reinserted: the user's deletion will
* be undone.
*
* Right now we don't preserve deletions when removing the Firefox Account, but
* we could do so if we were willing to trade local database space to handle this
* possible situation.
*/
fileprivate func collapseMirrorIntoLocalPreservingDeletions(_ preserveDeletions: Bool) -> Success {
// As implemented, this won't work correctly without ON DELETE CASCADE.
assert(SwiftData.EnableForeignKeys)
// 1. Wait until we commit to complain about constraint violations.
let deferForeignKeys =
"PRAGMA defer_foreign_keys = ON"
// 2. Drop anything from local that's deleted. We don't need to track the
// deletion now. Optional: keep them around if they're non-uploaded changes.
let removeLocalDeletions =
"DELETE FROM \(TableBookmarksLocal) WHERE is_deleted IS 1 " +
(preserveDeletions ? "AND sync_status IS NOT \(SyncStatus.changed.rawValue)" : "")
// 3. Mark everything in local as New.
let markLocalAsNew =
"UPDATE \(TableBookmarksLocal) SET sync_status = \(SyncStatus.new.rawValue)"
// 4. Insert into local anything not overridden left in mirror.
// Note that we use the server modified time as our substitute local modified time.
// This will provide an ounce of conflict avoidance if the user re-links the same
// account at a later date.
let copyMirrorContents =
"INSERT OR IGNORE INTO \(TableBookmarksLocal) " +
"(sync_status, local_modified, " +
" guid, date_added, type, bmkUri, title, parentid, parentName, feedUri, siteUri, pos," +
" description, tags, keyword, folderName, queryId, faviconID) " +
"SELECT " +
"\(SyncStatus.new.rawValue) AS sync_status, " +
"server_modified AS local_modified, " +
"guid, date_added, type, bmkUri, title, parentid, parentName, " +
"feedUri, siteUri, pos, description, tags, keyword, folderName, queryId, faviconID " +
"FROM \(TableBookmarksMirror) WHERE is_overridden IS 0"
// 5.(pre) I have a database right in front of me that violates an assumption: a full
// bookmarksMirrorStructure and an empty bookmarksMirror. Clean up, just in case.
let removeOverriddenStructure =
"DELETE FROM \(TableBookmarksMirrorStructure) WHERE parent IN (SELECT guid FROM \(TableBookmarksMirror) WHERE is_overridden IS 1)"
// 5. Insert into localStructure anything left in mirrorStructure.
// This won't copy the structure of any folders that were already overridden --
// we already deleted those, and the deletions cascaded.
let copyMirrorStructure =
"INSERT INTO \(TableBookmarksLocalStructure) SELECT * FROM \(TableBookmarksMirrorStructure)"
// 6. Blank the mirror.
let removeMirrorStructure =
"DELETE FROM \(TableBookmarksMirrorStructure)"
let removeMirrorContents =
"DELETE FROM \(TableBookmarksMirror)"
return db.run([
deferForeignKeys,
removeLocalDeletions,
markLocalAsNew,
copyMirrorContents,
removeOverriddenStructure,
copyMirrorStructure,
removeMirrorStructure,
removeMirrorContents,
])
}
}
extension SQLiteBookmarks: AccountRemovalDelegate {
public func onRemovedAccount() -> Success {
return self.collapseMirrorIntoLocalPreservingDeletions(false)
}
}
extension SQLiteBookmarks: ResettableSyncStorage {
public func resetClient() -> Success {
// Flip flags to prompt a re-sync.
//
// We copy the mirror to local, preserving local changes, apart from
// deletions of records that were never synced.
//
// Records that match the server record that we'll redownload will be
// marked as Synced and won't be reuploaded.
//
// Records that are present locally but aren't on the server will be
// uploaded.
//
return self.collapseMirrorIntoLocalPreservingDeletions(true)
}
}