mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-20 19:33:08 +09:00
146 lines
5.9 KiB
Swift
146 lines
5.9 KiB
Swift
/* 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)
|
|
}
|
|
}
|