Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Bookmarks DB Setup #3143

Merged
merged 14 commits into from
Aug 5, 2024
21 changes: 17 additions & 4 deletions Core/BookmarksStateValidation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,23 @@ import CoreData
import Bookmarks
import Persistence

public class BookmarksStateValidation {
public protocol BookmarksStateValidation {

func validateInitialState(context: NSManagedObjectContext,
validationError: BookmarksStateValidator.ValidationError) -> Bool

func validateBookmarksStructure(context: NSManagedObjectContext)
}

public class BookmarksStateValidator: BookmarksStateValidation {

enum Constants {
static let bookmarksDBIsInitialized = "bookmarksDBIsInitialized"
}

public enum ValidationError {
case bookmarksStructureLost
case bookmarksStructureNotRecovered
case bookmarksStructureBroken(additionalParams: [String: String])
case validatorError(Error)
}
Expand All @@ -43,18 +52,22 @@ public class BookmarksStateValidation {
self.errorHandler = errorHandler
}

public func validateInitialState(context: NSManagedObjectContext) {
guard keyValueStore.object(forKey: Constants.bookmarksDBIsInitialized) != nil else { return }
public func validateInitialState(context: NSManagedObjectContext,
validationError: ValidationError) -> Bool {
guard keyValueStore.object(forKey: Constants.bookmarksDBIsInitialized) != nil else { return true }

let fetch = BookmarkEntity.fetchRequest()
do {
let count = try context.count(for: fetch)
if count == 0 {
errorHandler(.bookmarksStructureLost)
errorHandler(validationError)
return false
}
} catch {
errorHandler(.validatorError(error))
}

return true
}

public func validateBookmarksStructure(context: NSManagedObjectContext) {
Expand Down
11 changes: 9 additions & 2 deletions Core/LegacyBookmarksStoreMigration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@ public class LegacyBookmarksStoreMigration {
}
} else {
// Initialize structure if needed
BookmarkUtils.prepareLegacyFoldersStructure(in: context)
do {
try BookmarkUtils.prepareLegacyFoldersStructure(in: context)
} catch {
Pixel.fire(pixel: .debugBookmarksInitialStructureQueryFailed, error: error)
Thread.sleep(forTimeInterval: 1)
fatalError("Could not prepare Bookmarks DB structure")
}

if context.hasChanges {
do {
try context.save(onErrorFire: .bookmarksCouldNotPrepareDatabase)
Expand Down Expand Up @@ -178,7 +185,7 @@ public class LegacyBookmarksStoreMigration {
} catch {
destination.reset()

BookmarkUtils.prepareLegacyFoldersStructure(in: destination)
try? BookmarkUtils.prepareLegacyFoldersStructure(in: destination)
do {
try destination.save(onErrorFire: .bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration)
} catch {
Expand Down
10 changes: 5 additions & 5 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,9 @@ extension Pixel {
case adAttributionLogicWrongVendorOnSuccessfulCompilation
case adAttributionLogicWrongVendorOnFailedCompilation

case debugBookmarksInitialStructureQueryFailed
case debugBookmarksStructureLost
case debugBookmarksStructureNotRecovered
case debugBookmarksInvalidRoots
case debugBookmarksValidationFailed

Expand Down Expand Up @@ -589,8 +591,6 @@ extension Pixel {
case syncDeleteAccountError
case syncLoginExistingAccountError

case syncWrongEnvironment

case swipeTabsUsedDaily
case swipeToOpenNewTab

Expand Down Expand Up @@ -1210,8 +1210,10 @@ extension Pixel.Event {
return "m_compilation_result_\(result)_time_\(waitTime)_state_\(appState)"

case .emailAutofillKeychainError: return "m_email_autofill_keychain_error"


case .debugBookmarksInitialStructureQueryFailed: return "m_d_bookmarks-initial-structure-query-failed"
case .debugBookmarksStructureLost: return "m_d_bookmarks_structure_lost"
case .debugBookmarksStructureNotRecovered: return "m_d_bookmarks_structure_not_recovered"
case .debugBookmarksInvalidRoots: return "m_d_bookmarks_invalid_roots"
case .debugBookmarksValidationFailed: return "m_d_bookmarks_validation_failed"

Expand Down Expand Up @@ -1295,8 +1297,6 @@ extension Pixel.Event {
case .syncDeleteAccountError: return "m_d_sync_delete_account_error"
case .syncLoginExistingAccountError: return "m_d_sync_login_existing_account_error"

case .syncWrongEnvironment: return "m_d_sync_wrong_environment_u"

case .swipeTabsUsedDaily: return "m_swipe-tabs-used-daily"
case .swipeToOpenNewTab: return "m_addressbar_swipe_new_tab"

Expand Down
6 changes: 5 additions & 1 deletion DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@
987130C7294AAB9F00AB05E0 /* MenuBookmarksViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C1294AAB9E00AB05E0 /* MenuBookmarksViewModelTests.swift */; };
987130C8294AAB9F00AB05E0 /* BookmarksTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C2294AAB9E00AB05E0 /* BookmarksTestHelpers.swift */; };
987130C9294AAB9F00AB05E0 /* BookmarkUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C3294AAB9E00AB05E0 /* BookmarkUtilsTests.swift */; };
987243142C5232B5007ECC76 /* BookmarksDatabaseSetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987243132C5232B5007ECC76 /* BookmarksDatabaseSetupTests.swift */; };
9872D205247DCAC100CEF398 /* TabPreviewsSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9872D204247DCAC100CEF398 /* TabPreviewsSource.swift */; };
9874F9EE2187AFCE00CAF33D /* Themable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9874F9ED2187AFCE00CAF33D /* Themable.swift */; };
9875E00722316B8400B1373F /* Instruments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9875E00622316B8400B1373F /* Instruments.swift */; };
Expand Down Expand Up @@ -2193,6 +2194,7 @@
987130C1294AAB9E00AB05E0 /* MenuBookmarksViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuBookmarksViewModelTests.swift; sourceTree = "<group>"; };
987130C2294AAB9E00AB05E0 /* BookmarksTestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksTestHelpers.swift; sourceTree = "<group>"; };
987130C3294AAB9E00AB05E0 /* BookmarkUtilsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkUtilsTests.swift; sourceTree = "<group>"; };
987243132C5232B5007ECC76 /* BookmarksDatabaseSetupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksDatabaseSetupTests.swift; sourceTree = "<group>"; };
9872D204247DCAC100CEF398 /* TabPreviewsSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPreviewsSource.swift; sourceTree = "<group>"; };
9874F9ED2187AFCE00CAF33D /* Themable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Themable.swift; sourceTree = "<group>"; };
9875E00622316B8400B1373F /* Instruments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instruments.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5443,6 +5445,7 @@
85BA58561F34F61C00C6E8CA /* AppUserDefaultsTests.swift */,
4B62C4B925B930DD008912C6 /* AppConfigurationFetchTests.swift */,
85AFA1202B45D14F0028A504 /* BookmarksMigrationAssertionTests.swift */,
987243132C5232B5007ECC76 /* BookmarksDatabaseSetupTests.swift */,
);
name = Application;
sourceTree = "<group>";
Expand Down Expand Up @@ -7320,6 +7323,7 @@
EEFE9C732A603CE9005B0A26 /* NetworkProtectionStatusViewModelTests.swift in Sources */,
F13B4BF91F18CA0600814661 /* TabsModelTests.swift in Sources */,
F1BDDBFD2C340D9C00459306 /* SubscriptionContainerViewModelTests.swift in Sources */,
987243142C5232B5007ECC76 /* BookmarksDatabaseSetupTests.swift in Sources */,
98B31290218CCB2200E54DE1 /* MockDependencyProvider.swift in Sources */,
CBDD5DDF29A6736A00832877 /* APIHeadersTests.swift in Sources */,
986B45D0299E30A50089D2D7 /* BookmarkEntityTests.swift in Sources */,
Expand Down Expand Up @@ -10281,7 +10285,7 @@
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 179.0.0;
version = 180.0.0;
};
};
9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/DuckDuckGo/BrowserServicesKit",
"state" : {
"revision" : "dfddead0e1e4735a021d3affb05b64fea561a807",
"version" : "179.0.0"
"revision" : "92ecebfb4172ab9561959a07d7ef7037aea8c6e1",
"version" : "180.0.0"
}
},
{
Expand All @@ -59,17 +59,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/duckduckgo-autofill.git",
"state" : {
"revision" : "9fea1c6762db726328b14bb9ebfd6508849eae28",
"version" : "12.1.0"
"revision" : "2b81745565db09eee8c1cd44d38eefa1011a9f0a",
"version" : "12.0.1"
}
},
{
"identity" : "grdb.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/GRDB.swift.git",
"state" : {
"revision" : "4225b85c9a0c50544e413a1ea1e502c802b44b35",
"version" : "2.4.0"
"revision" : "9f049d7b97b1e68ffd86744b500660d34a9e79b8",
"version" : "2.3.0"
}
},
{
Expand Down Expand Up @@ -138,7 +138,7 @@
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
"revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b",
"version" : "1.4.0"
Expand Down
81 changes: 48 additions & 33 deletions DuckDuckGo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ import WebKit
Pixel.isDryRun = true
_ = DefaultUserAgentManager.shared
Database.shared.loadStore { _, _ in }
_ = BookmarksDatabaseSetup(crashOnError: true).loadStoreAndMigrate(bookmarksDatabase: bookmarksDatabase)
_ = BookmarksDatabaseSetup().loadStoreAndMigrate(bookmarksDatabase: bookmarksDatabase)
window?.rootViewController = UIStoryboard.init(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
return true
}
Expand Down Expand Up @@ -209,9 +209,18 @@ import WebKit
DatabaseMigration.migrate(to: context)
}

if BookmarksDatabaseSetup(crashOnError: !shouldPresentInsufficientDiskSpaceAlertAndCrash)
.loadStoreAndMigrate(bookmarksDatabase: bookmarksDatabase) {
// MARK: post-Bookmarks migration logic
switch BookmarksDatabaseSetup().loadStoreAndMigrate(bookmarksDatabase: bookmarksDatabase) {
case .success:
break
case .failure(let error):
Pixel.fire(pixel: .bookmarksCouldNotLoadDatabase,
error: error)
if error.isDiskFull {
shouldPresentInsufficientDiskSpaceAlertAndCrash = true
} else {
Thread.sleep(forTimeInterval: 1)
fatalError("Could not create database stack: \(error.localizedDescription)")
}
}

WidgetCenter.shared.reloadAllTimelines()
Expand Down Expand Up @@ -308,35 +317,40 @@ import WebKit
let tabsModel = prepareTabsModel(previewsSource: previewsSource)

privacyProDataReporter.injectTabsModel(tabsModel)

if shouldPresentInsufficientDiskSpaceAlertAndCrash {

let main = MainViewController(bookmarksDatabase: bookmarksDatabase,
bookmarksDatabaseCleaner: syncDataProviders.bookmarksAdapter.databaseCleaner,
historyManager: historyManager,
homePageConfiguration: homePageConfiguration,
syncService: syncService,
syncDataProviders: syncDataProviders,
appSettings: AppDependencyProvider.shared.appSettings,
previewsSource: previewsSource,
tabsModel: tabsModel,
syncPausedStateManager: syncErrorHandler,
privacyProDataReporter: privacyProDataReporter)

main.loadViewIfNeeded()
syncErrorHandler.alertPresenter = main

window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = main
window?.makeKeyAndVisible()
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = BlankSnapshotViewController(appSettings: AppDependencyProvider.shared.appSettings)
window?.makeKeyAndVisible()

if shouldPresentInsufficientDiskSpaceAlertAndCrash {
presentInsufficientDiskSpaceAlert()
}
} else {
let main = MainViewController(bookmarksDatabase: bookmarksDatabase,
bookmarksDatabaseCleaner: syncDataProviders.bookmarksAdapter.databaseCleaner,
historyManager: historyManager,
homePageConfiguration: homePageConfiguration,
syncService: syncService,
syncDataProviders: syncDataProviders,
appSettings: AppDependencyProvider.shared.appSettings,
previewsSource: previewsSource,
tabsModel: tabsModel,
syncPausedStateManager: syncErrorHandler,
privacyProDataReporter: privacyProDataReporter)

main.loadViewIfNeeded()
syncErrorHandler.alertPresenter = main

window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = main
window?.makeKeyAndVisible()

autoClear = AutoClear(worker: main)
let applicationState = application.applicationState
Task {
await autoClear?.clearDataIfEnabled(applicationState: .init(with: applicationState))
await vpnWorkaround.installRedditSessionWorkaround()
autoClear = AutoClear(worker: main)
let applicationState = application.applicationState
Task {
await autoClear?.clearDataIfEnabled(applicationState: .init(with: applicationState))
await vpnWorkaround.installRedditSessionWorkaround()
}
}

AppDependencyProvider.shared.voiceSearchHelper.migrateSettingsFlagIfNecessary()
Expand Down Expand Up @@ -473,10 +487,6 @@ import WebKit
guard !testing else { return }

syncService.initializeIfNeeded()
if syncService.authState == .active &&
(InternalUserStore().isInternalUser == false && syncService.serverEnvironment == .development) {
UniquePixel.fire(pixel: .syncWrongEnvironment)
}
syncDataProviders.setUpDatabaseCleanersIfNeeded(syncService: syncService)

if !(overlayWindow?.rootViewController is AuthenticationViewController) {
Expand Down Expand Up @@ -1052,6 +1062,11 @@ private extension Error {
if let underlyingError = nsError.userInfo["NSUnderlyingError"] as? NSError, underlyingError.code == 13 {
return true
}

if nsError.userInfo["NSSQLiteErrorDomain"] as? Int == 13 {
return true
}

return false
}

Expand Down
Loading
Loading