Skip to content

Commit ccba159

Browse files
committed
Back up and repair database
1 parent 9439bca commit ccba159

File tree

12 files changed

+330
-32
lines changed

12 files changed

+330
-32
lines changed

Mixin.xcodeproj/project.pbxproj

+16
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@
595595
7C5DFE34284F3EA3008733FC /* UserCenterTableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5DFE33284F3EA3008733FC /* UserCenterTableHeaderView.swift */; };
596596
7C6132B627953B15002777EE /* DeleteAccountAbortWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6132B527953B15002777EE /* DeleteAccountAbortWindow.swift */; };
597597
7C6132B827953B4F002777EE /* DeleteAccountAbortWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7C6132B727953B4F002777EE /* DeleteAccountAbortWindow.xib */; };
598+
7C62922229B9699000B3596C /* DatabaseBackupJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C62922129B9699000B3596C /* DatabaseBackupJob.swift */; };
598599
7C66E7B82743988500FF24C1 /* ProfileDescriptionLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C66E7B72743988500FF24C1 /* ProfileDescriptionLabel.swift */; };
599600
7C66F0272689D1FE006D8462 /* HomeAppsDragInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C66F0262689D1FE006D8462 /* HomeAppsDragInteraction.swift */; };
600601
7C66F029268A0384006D8462 /* AppPageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C66F028268A0384006D8462 /* AppPageCell.swift */; };
@@ -636,6 +637,9 @@
636637
7CCC801C292DC68E000B4200 /* ImageCropViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCC801A292DC68E000B4200 /* ImageCropViewController.swift */; };
637638
7CCE65A828D69D1D00FE944A /* TransferActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCE65A628D69D1D00FE944A /* TransferActionView.swift */; };
638639
7CCE65A928D69D1D00FE944A /* TransferActionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7CCE65A728D69D1D00FE944A /* TransferActionView.xib */; };
640+
7CD9C17229B9BE94008F8D85 /* DatabaseBackupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CD9C17129B9BE94008F8D85 /* DatabaseBackupManager.swift */; };
641+
7CD9C17429B9DDE0008F8D85 /* DatabaseRepairViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CD9C17329B9DDE0008F8D85 /* DatabaseRepairViewController.swift */; };
642+
7CD9C17829BB306F008F8D85 /* DatabaseFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CD9C17729BB306F008F8D85 /* DatabaseFile.swift */; };
639643
7CDBA58A28F64E5000AC3777 /* WalletTransferSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDBA58928F64E5000AC3777 /* WalletTransferSearchResultsViewController.swift */; };
640644
7CDBA58E28F7B6CB00AC3777 /* TransferSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDBA58D28F7B6CB00AC3777 /* TransferSearchViewController.swift */; };
641645
7CDF316C29890FB200421808 /* ConversationFontSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDF316B29890FB200421808 /* ConversationFontSet.swift */; };
@@ -1619,6 +1623,7 @@
16191623
7C5DFE33284F3EA3008733FC /* UserCenterTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCenterTableHeaderView.swift; sourceTree = "<group>"; };
16201624
7C6132B527953B15002777EE /* DeleteAccountAbortWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccountAbortWindow.swift; sourceTree = "<group>"; };
16211625
7C6132B727953B4F002777EE /* DeleteAccountAbortWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DeleteAccountAbortWindow.xib; sourceTree = "<group>"; };
1626+
7C62922129B9699000B3596C /* DatabaseBackupJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseBackupJob.swift; sourceTree = "<group>"; };
16221627
7C66E7B72743988500FF24C1 /* ProfileDescriptionLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDescriptionLabel.swift; sourceTree = "<group>"; };
16231628
7C66F0262689D1FE006D8462 /* HomeAppsDragInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeAppsDragInteraction.swift; sourceTree = "<group>"; };
16241629
7C66F028268A0384006D8462 /* AppPageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPageCell.swift; sourceTree = "<group>"; };
@@ -1660,6 +1665,9 @@
16601665
7CCC801A292DC68E000B4200 /* ImageCropViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCropViewController.swift; sourceTree = "<group>"; };
16611666
7CCE65A628D69D1D00FE944A /* TransferActionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferActionView.swift; sourceTree = "<group>"; };
16621667
7CCE65A728D69D1D00FE944A /* TransferActionView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TransferActionView.xib; sourceTree = "<group>"; };
1668+
7CD9C17129B9BE94008F8D85 /* DatabaseBackupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseBackupManager.swift; sourceTree = "<group>"; };
1669+
7CD9C17329B9DDE0008F8D85 /* DatabaseRepairViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseRepairViewController.swift; sourceTree = "<group>"; };
1670+
7CD9C17729BB306F008F8D85 /* DatabaseFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseFile.swift; sourceTree = "<group>"; };
16631671
7CDBA58928F64E5000AC3777 /* WalletTransferSearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTransferSearchResultsViewController.swift; sourceTree = "<group>"; };
16641672
7CDBA58D28F7B6CB00AC3777 /* TransferSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferSearchViewController.swift; sourceTree = "<group>"; };
16651673
7CDF316B29890FB200421808 /* ConversationFontSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationFontSet.swift; sourceTree = "<group>"; };
@@ -2365,6 +2373,8 @@
23652373
7BA24D7225342575004906AD /* HomeOverlaysCoordinator.swift */,
23662374
7BA24D76253438B9004906AD /* ViewPanningController.swift */,
23672375
94D9DF6025F89D6E00FC2F28 /* BulletinContent.swift */,
2376+
7CD9C17729BB306F008F8D85 /* DatabaseFile.swift */,
2377+
7CD9C17129B9BE94008F8D85 /* DatabaseBackupManager.swift */,
23682378
);
23692379
path = Model;
23702380
sourceTree = "<group>";
@@ -3285,6 +3295,7 @@
32853295
DF75FF4324FE61E1008A7CF3 /* UpdateViewController.swift */,
32863296
7BDB4724215A666B008B21F9 /* SignalLoadingViewController.swift */,
32873297
DFD1FBC62302CB7A00C570D4 /* DatabaseUpgradeViewController.swift */,
3298+
7CD9C17329B9DDE0008F8D85 /* DatabaseRepairViewController.swift */,
32883299
DFB2062721ABC088006E4341 /* RestoreViewController.swift */,
32893300
7B63A8622431C9EE00D0F7C7 /* CirclesViewController.swift */,
32903301
7BB0F90F2434821000BEDA97 /* CircleEditorViewController.swift */,
@@ -3720,6 +3731,7 @@
37203731
947F4AD525866D6C00B0A5F9 /* InitializeFTSJob.swift */,
37213732
842347ED2695BA6400009A39 /* InitializeBotJob.swift */,
37223733
94FCB83A264683D900CCC8FD /* TranscriptAttachmentUploadJob.swift */,
3734+
7C62922129B9699000B3596C /* DatabaseBackupJob.swift */,
37233735
);
37243736
path = Job;
37253737
sourceTree = "<group>";
@@ -4438,6 +4450,7 @@
44384450
files = (
44394451
7BF49DD320C3DBAC00A8510E /* CaptchaManager.swift in Sources */,
44404452
DF8CECE11FC3054700E40064 /* TransferTypeCell.swift in Sources */,
4453+
7CD9C17829BB306F008F8D85 /* DatabaseFile.swift in Sources */,
44414454
7BCB8C8422BB56B8002A13CC /* DataAndStorageSettingsViewController.swift in Sources */,
44424455
9BB351671FB19ECB00EDDD2C /* ConversationDateHeaderView.swift in Sources */,
44434456
DF2819752014669E001EE5FA /* RefreshAccountJob.swift in Sources */,
@@ -4670,6 +4683,7 @@
46704683
E041064B23C5C3BC00A6F08E /* CoreTextLabelDelegate.swift in Sources */,
46714684
7B915F74215FB0C100A562C6 /* GiphySearchViewController.swift in Sources */,
46724685
7B7DACA623505793006AA2AC /* AudioCell.swift in Sources */,
4686+
7CD9C17429B9DDE0008F8D85 /* DatabaseRepairViewController.swift in Sources */,
46734687
5E5CA86D2674B09100C1E113 /* ScreenLockSettingViewController.swift in Sources */,
46744688
DF5D9F251F9C79E10036D5FD /* LocalizedExtension.swift in Sources */,
46754689
7B4FCCE02440A66600360F65 /* SolidBackgroundColorImageView.swift in Sources */,
@@ -4788,6 +4802,7 @@
47884802
7BFDB73920DA41E3005673CC /* Quote.swift in Sources */,
47894803
7B51DDB5223A489F008ACDBB /* LoginContext.swift in Sources */,
47904804
DF5D9F281F9C79E10036D5FD /* UIApplicationExtension.swift in Sources */,
4805+
7C62922229B9699000B3596C /* DatabaseBackupJob.swift in Sources */,
47914806
DF7A4B4A1FCE6EE200F21BCB /* UIViewExtension.swift in Sources */,
47924807
7C5823D5268966A1003AA142 /* HomeAppsFolderViewController.swift in Sources */,
47934808
7BD00F1E2559711A004D8814 /* WalletSearchResultsViewController.swift in Sources */,
@@ -4987,6 +5002,7 @@
49875002
7BEE5351222D0E5C008D3911 /* ConversationExtensionCell.swift in Sources */,
49885003
DF8CECF21FC4256D00E40064 /* BlockUserCell.swift in Sources */,
49895004
7CC730502745F95D002780F5 /* StickerStore.swift in Sources */,
5005+
7CD9C17229B9BE94008F8D85 /* DatabaseBackupManager.swift in Sources */,
49905006
7B2E56B0244EA6BB0073102C /* SettingsFooterView.swift in Sources */,
49915007
7BD7534E2182CDCE00BAC172 /* IconPrefixedTextMessageCell.swift in Sources */,
49925008
7B369206233A3314007321A7 /* SharedMediaViewController.swift in Sources */,

Mixin/AppDelegate.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
3333
if #available(iOS 15.0, *), !ProcessInfo.processInfo.isiOSAppOnMac {
3434
UITableView.appearance().sectionHeaderTopPadding = 0
3535
}
36+
addObservers()
3637
checkLogin()
3738
ScreenLockManager.shared.lockScreenIfNeeded()
3839
checkJailbreak()
3940
configAnalytics()
4041
pendingShortcutItem = launchOptions?[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem
41-
addObservers()
4242
Logger.general.info(category: "AppDelegate", message: "App \(Bundle.main.shortVersion)(\(Bundle.main.bundleVersion)) did finish launching with state: \(UIApplication.shared.applicationStateString), device: \(Device.current.machineName) \(ProcessInfo.processInfo.operatingSystemVersionString), id: \(Device.current.id)")
4343
if UIApplication.shared.applicationState == .background {
4444
MixinService.isStopProcessMessages = false
@@ -197,6 +197,7 @@ extension AppDelegate {
197197
NotificationCenter.default.addObserver(self, selector: #selector(handleClockSkew), name: MixinService.clockSkewDetectedNotification, object: nil)
198198
NotificationCenter.default.addObserver(self, selector: #selector(webSocketDidConnect), name: WebSocketService.didConnectNotification, object: nil)
199199
NotificationCenter.default.addObserver(JobService.shared, selector: #selector(JobService.restoreJobs), name: WebSocketService.didSendListPendingMessageNotification, object: nil)
200+
NotificationCenter.default.addObserver(self, selector: #selector(databaseCorrupted), name: AppGroupUserDefaults.User.databaseCorruptedNotification, object: nil)
200201
}
201202

202203
@objc func webSocketDidConnect() {
@@ -257,6 +258,9 @@ extension AppDelegate {
257258
}
258259
}
259260

261+
@objc func databaseCorrupted() {
262+
mainWindow.rootViewController = makeInitialViewController()
263+
}
260264
}
261265

262266
extension AppDelegate {
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Foundation
2+
import MixinServices
3+
import GRDB
4+
5+
class DatabaseBackupJob: AsynchronousJob {
6+
7+
override func getJobId() -> String {
8+
"database-backup"
9+
}
10+
11+
override func execute() -> Bool {
12+
do {
13+
try UserDatabase.current.writeWithoutTransaction { _ in
14+
try DatabaseFile.copy(at: .original, to: .temp)
15+
}
16+
let dbQueue = try DatabaseQueue(path: DatabaseFile.temp.db.path)
17+
try dbQueue.write { db in
18+
try db.execute(sql: "PRAGMA integrity_check")
19+
}
20+
try DatabaseFile.copy(at: .temp, to: .backup)
21+
try DatabaseFile.removeIfExists(.temp)
22+
AppGroupUserDefaults.User.lastDatabaseBackupDate = Date()
23+
finishJob()
24+
} catch {
25+
Logger.general.error(category: "BackupDatabaseJob", message: "Backup database failed: \(error)")
26+
reporter.report(error: error)
27+
}
28+
return true
29+
}
30+
31+
}

Mixin/UserInterface/Controllers/Common/InitialViewControllerFactory.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import UIKit
22
import MixinServices
33

44
func makeInitialViewController(isUsernameJustInitialized: Bool = false) -> UIViewController {
5-
if AppGroupUserDefaults.Account.isClockSkewed {
5+
if AppGroupUserDefaults.User.isDatabaseCorrupted {
6+
return DatabaseRepairViewController.instance()
7+
} else if AppGroupUserDefaults.Account.isClockSkewed {
68
if let viewController = AppDelegate.current.mainWindow.rootViewController as? ClockSkewViewController {
79
viewController.checkFailed()
810
return viewController
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import UIKit
2+
import MixinServices
3+
4+
class DatabaseRepairViewController: UIViewController {
5+
6+
@IBOutlet weak var repairButton: RoundedButton!
7+
@IBOutlet weak var stackView: UIStackView!
8+
@IBOutlet weak var activityIndicator: ActivityIndicatorView!
9+
10+
class func instance() -> DatabaseRepairViewController {
11+
R.storyboard.home.repair_database()!
12+
}
13+
14+
@IBAction func repairAction(_ sender: Any) {
15+
repairButton.isHidden = true
16+
stackView.isHidden = false
17+
activityIndicator.startAnimating()
18+
if DatabaseFile.exists(.backup) {
19+
do {
20+
try DatabaseFile.removeIfExists(.original)
21+
try DatabaseFile.copy(at: .backup, to: .original)
22+
let lastBackupDate = AppGroupUserDefaults.User.lastDatabaseBackupDate ?? Date()
23+
let formattedDate = DateFormatter.dateFull.string(from: lastBackupDate)
24+
stackView.isHidden = true
25+
alert(nil, message: R.string.localizable.repair_chat_history_success(formattedDate)) { _ in
26+
AppGroupUserDefaults.User.isDatabaseCorrupted = false
27+
}
28+
} catch {
29+
LoginManager.shared.logout(reason: "Failed to repair database")
30+
}
31+
} else {
32+
LoginManager.shared.logout(reason: "Failed to repair database")
33+
}
34+
}
35+
36+
}

Mixin/UserInterface/Controllers/Home/HomeViewController.swift

+1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ class HomeViewController: UIViewController {
151151
if SpotlightManager.isAvailable {
152152
SpotlightManager.shared.indexIfNeeded()
153153
}
154+
DatabaseBackupManager.shared.backupIfNeeded()
154155
}
155156
UIApplication.homeContainerViewController?.clipSwitcher.loadClipsFromPreviousSession()
156157
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Foundation
2+
import MixinServices
3+
4+
class DatabaseBackupManager: NSObject {
5+
6+
static let shared = DatabaseBackupManager()
7+
8+
override init() {
9+
super.init()
10+
NotificationCenter.default.addObserver(self, selector: #selector(backupIfNeeded), name: UIApplication.didBecomeActiveNotification, object: nil)
11+
}
12+
13+
@objc func backupIfNeeded() {
14+
guard LoginManager.shared.isLoggedIn else {
15+
return
16+
}
17+
let lastDatabaseBackupDate = AppGroupUserDefaults.User.lastDatabaseBackupDate
18+
if lastDatabaseBackupDate == nil || -lastDatabaseBackupDate!.timeIntervalSinceNow > TimeInterval.hour * 2 {
19+
ConcurrentJobQueue.shared.addJob(job: DatabaseBackupJob())
20+
}
21+
}
22+
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Foundation
2+
import MixinServices
3+
4+
enum DatabaseFile {
5+
6+
case original
7+
case backup
8+
case temp
9+
10+
private var name: String {
11+
switch self {
12+
case .original:
13+
return "mixin.db"
14+
case .backup:
15+
return "mixin-backup.db"
16+
case .temp:
17+
return "mixin-backup-temp.db"
18+
}
19+
}
20+
21+
var db: URL {
22+
AppGroupContainer.accountUrl.appendingPathComponent("\(name)", isDirectory: false)
23+
}
24+
25+
var shm: URL {
26+
AppGroupContainer.accountUrl.appendingPathComponent("\(name)-shm", isDirectory: false)
27+
}
28+
29+
var wal: URL {
30+
AppGroupContainer.accountUrl.appendingPathComponent("\(name)-wal", isDirectory: false)
31+
}
32+
33+
static func removeIfExists(_ file: DatabaseFile) throws {
34+
if FileManager.default.fileExists(atPath: file.db.path) {
35+
try FileManager.default.removeItem(at: file.db)
36+
}
37+
if FileManager.default.fileExists(atPath: file.wal.path) {
38+
try FileManager.default.removeItem(at: file.wal)
39+
}
40+
if FileManager.default.fileExists(atPath: file.shm.path) {
41+
try FileManager.default.removeItem(at: file.shm)
42+
}
43+
}
44+
45+
static func copy(at srcFile: DatabaseFile, to dstFIle: DatabaseFile) throws {
46+
try removeIfExists(dstFIle)
47+
try FileManager.default.copyItem(at: srcFile.db, to: dstFIle.db)
48+
try FileManager.default.copyItem(at: srcFile.wal, to: dstFIle.wal)
49+
try FileManager.default.copyItem(at: srcFile.shm, to: dstFIle.shm)
50+
}
51+
52+
static func exists(_ file: DatabaseFile) -> Bool {
53+
FileManager.default.fileExists(atPath: file.db.path) &&
54+
FileManager.default.fileExists(atPath: file.wal.path) &&
55+
FileManager.default.fileExists(atPath: file.shm.path)
56+
}
57+
58+
}
59+

0 commit comments

Comments
 (0)