Merge branch 'remove-unused-code' into voice-calls-2

This commit is contained in:
Ryan Zhao 2022-04-04 11:08:54 +10:00
commit 36094eed7f
66 changed files with 85 additions and 6480 deletions

View File

@ -13,7 +13,6 @@
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */; };
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */; };
340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC883204DAC8C007AEB0F /* OWSSoundSettingsViewController.m */; };
340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC88E204DAC8C007AEB0F /* OWSBackupSettingsViewController.m */; };
340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */; };
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC89A204DAC8D007AEB0F /* OWSConversationSettingsViewController.m */; };
341341EF2187467A00192D59 /* ConversationViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 341341EE2187467900192D59 /* ConversationViewModel.m */; };
@ -21,8 +20,6 @@
3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3430FE171F7751D4000EC51B /* GiphyAPI.swift */; };
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; };
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34386A53207D271C009F5D9C /* NeverClearView.swift */; };
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */; };
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */; };
346129991FD1E4DA00532771 /* SignalApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129971FD1E4D900532771 /* SignalApp.m */; };
34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 34661FB720C1C0D60056EDD6 /* message_sent.aiff */; };
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; };
@ -34,13 +31,6 @@
3496955D219B605E00DCFE74 /* PhotoCollectionPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496955A219B605E00DCFE74 /* PhotoCollectionPickerController.swift */; };
3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496955B219B605E00DCFE74 /* PhotoLibrary.swift */; };
3496956021A2FC8100DCFE74 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3496955F21A2FC8100DCFE74 /* CloudKit.framework */; };
3496956E21A301A100DCFE74 /* OWSBackupExportJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956221A301A100DCFE74 /* OWSBackupExportJob.m */; };
3496956F21A301A100DCFE74 /* OWSBackupLazyRestore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496956321A301A100DCFE74 /* OWSBackupLazyRestore.swift */; };
3496957021A301A100DCFE74 /* OWSBackupIO.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956521A301A100DCFE74 /* OWSBackupIO.m */; };
3496957121A301A100DCFE74 /* OWSBackupImportJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956621A301A100DCFE74 /* OWSBackupImportJob.m */; };
3496957221A301A100DCFE74 /* OWSBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956921A301A100DCFE74 /* OWSBackup.m */; };
3496957321A301A100DCFE74 /* OWSBackupJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956A21A301A100DCFE74 /* OWSBackupJob.m */; };
3496957421A301A100DCFE74 /* OWSBackupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */; };
34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */; };
34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A8B3502190A40E00218A25 /* MediaAlbumView.swift */; };
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34ABC0E321DD20C500ED9469 /* ConversationMessageMapping.swift */; };
@ -510,24 +500,20 @@
C33FDCFA255A582000E217F9 /* SignalIOSProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB40255A580C00E217F9 /* SignalIOSProto.swift */; };
C33FDD03255A582000E217F9 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB49255A580C00E217F9 /* WeakTimer.swift */; };
C33FDD06255A582000E217F9 /* AppVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB4C255A580D00E217F9 /* AppVersion.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDD12255A582000E217F9 /* OWSPrimaryStorage+Loki.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB58255A580E00E217F9 /* OWSPrimaryStorage+Loki.m */; };
C33FDD13255A582000E217F9 /* OWSFailedAttachmentDownloadsJob.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB59255A580E00E217F9 /* OWSFailedAttachmentDownloadsJob.m */; };
C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB69255A580F00E217F9 /* FeatureFlags.swift */; };
C33FDD32255A582000E217F9 /* OWSOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB78255A581000E217F9 /* OWSOperation.m */; };
C33FDD3A255A582000E217F9 /* Notification+Loki.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB80255A581100E217F9 /* Notification+Loki.swift */; };
C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB8F255A581200E217F9 /* ParamParser.swift */; };
C33FDD53255A582000E217F9 /* OWSPrimaryStorage+keyFromIntLong.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB99255A581300E217F9 /* OWSPrimaryStorage+keyFromIntLong.m */; };
C33FDD5A255A582000E217F9 /* TSStorageHeaders.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBA0255A581400E217F9 /* TSStorageHeaders.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDD5B255A582000E217F9 /* OWSOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBA1255A581400E217F9 /* OWSOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDD68255A582000E217F9 /* SignalAccount.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBAE255A581500E217F9 /* SignalAccount.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDD6E255A582000E217F9 /* NSURLSessionDataTask+StatusCode.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */; };
C33FDD74255A582000E217F9 /* OWSPrimaryStorage+keyFromIntLong.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBBA255A581600E217F9 /* OWSPrimaryStorage+keyFromIntLong.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDD75255A582000E217F9 /* OWSPrimaryStorage+Loki.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBBB255A581600E217F9 /* OWSPrimaryStorage+Loki.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDD7C255A582000E217F9 /* SSKAsserts.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBC2255A581700E217F9 /* SSKAsserts.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */; };
C33FDD91255A582000E217F9 /* OWSMessageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD7255A581900E217F9 /* OWSMessageUtils.m */; };
C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD8255A581900E217F9 /* SignalIOS.pb.swift */; };
C33FDDA9255A582000E217F9 /* TSStorageKeys.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBEF255A581B00E217F9 /* TSStorageKeys.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDDB0255A582000E217F9 /* NSURLSessionDataTask+StatusCode.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDDB2255A582000E217F9 /* NSArray+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF8255A581C00E217F9 /* NSArray+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDDB3255A582000E217F9 /* OWSError.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF9255A581C00E217F9 /* OWSError.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -607,7 +593,6 @@
C38EF30C255B6DBF007E1867 /* OWSScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */; };
C38EF30D255B6DBF007E1867 /* OWSUnreadIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E3255B6DB9007E1867 /* OWSUnreadIndicator.m */; };
C38EF30E255B6DBF007E1867 /* FullTextSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E4255B6DB9007E1867 /* FullTextSearcher.swift */; };
C38EF30F255B6DBF007E1867 /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E5255B6DB9007E1867 /* AppPreferences.swift */; };
C38EF313255B6DBF007E1867 /* OWSUnreadIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2E9255B6DBA007E1867 /* OWSUnreadIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38EF317255B6DBF007E1867 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2ED255B6DBB007E1867 /* DisplayableText.swift */; };
C38EF31A255B6DBF007E1867 /* OWSAnyTouchGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F0255B6DBB007E1867 /* OWSAnyTouchGestureRecognizer.m */; };
@ -661,7 +646,6 @@
C38EF3C7255B6DE7007E1867 /* ImageEditorCanvasView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3B7255B6DE6007E1867 /* ImageEditorCanvasView.swift */; };
C38EF3EF255B6DF7007E1867 /* ThreadViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3D1255B6DEE007E1867 /* ThreadViewHelper.m */; };
C38EF3F0255B6DF7007E1867 /* ThreadViewHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF3D2255B6DEE007E1867 /* ThreadViewHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38EF3F1255B6DF7007E1867 /* OWSSearchBar.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF3D3255B6DEE007E1867 /* OWSSearchBar.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38EF3F2255B6DF7007E1867 /* DisappearingTimerConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3D4255B6DEE007E1867 /* DisappearingTimerConfigurationView.swift */; };
C38EF3F4255B6DF7007E1867 /* ContactCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3D6255B6DEF007E1867 /* ContactCellView.m */; };
C38EF3F5255B6DF7007E1867 /* OWSTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF3D7255B6DF0007E1867 /* OWSTextField.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -680,7 +664,6 @@
C38EF404255B6DF7007E1867 /* ContactTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF3E6255B6DF4007E1867 /* ContactTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38EF405255B6DF7007E1867 /* OWSButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3E7255B6DF5007E1867 /* OWSButton.swift */; };
C38EF407255B6DF7007E1867 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3E9255B6DF6007E1867 /* Toast.swift */; };
C38EF408255B6DF7007E1867 /* OWSSearchBar.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3EA255B6DF6007E1867 /* OWSSearchBar.m */; };
C38EF409255B6DF7007E1867 /* ContactTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3EB255B6DF6007E1867 /* ContactTableViewCell.m */; };
C38EF40A255B6DF7007E1867 /* OWSFlatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3EC255B6DF6007E1867 /* OWSFlatButton.swift */; };
C38EF40B255B6DF7007E1867 /* TappableStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3ED255B6DF6007E1867 /* TappableStackView.swift */; };
@ -774,7 +757,6 @@
C3D9E3C025676AD70040E4F3 /* TSAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAC2255A580200E217F9 /* TSAttachment.m */; };
C3D9E3C925676AF30040E4F3 /* TSYapDatabaseObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA90255A57FD00E217F9 /* TSYapDatabaseObject.m */; };
C3D9E3FA25676BCE0040E4F3 /* TSYapDatabaseObject.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAA1255A57FF00E217F9 /* TSYapDatabaseObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3D9E40C25676C100040E4F3 /* Storage+Conformances.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D9E40B25676C100040E4F3 /* Storage+Conformances.swift */; };
C3D9E41525676C320040E4F3 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB36255A580B00E217F9 /* Storage.swift */; };
C3D9E41F25676C870040E4F3 /* OWSPrimaryStorageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D9E41E25676C870040E4F3 /* OWSPrimaryStorageProtocol.swift */; };
C3D9E43125676D3D0040E4F3 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D9E43025676D3D0040E4F3 /* Configuration.swift */; };
@ -982,12 +964,10 @@
340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsOptionsViewController.m; sourceTree = "<group>"; };
340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsViewController.m; sourceTree = "<group>"; };
340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrivacySettingsTableViewController.m; sourceTree = "<group>"; };
340FC87F204DAC8C007AEB0F /* OWSBackupSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupSettingsViewController.h; sourceTree = "<group>"; };
340FC883204DAC8C007AEB0F /* OWSSoundSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSoundSettingsViewController.m; sourceTree = "<group>"; };
340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQRCodeScanningViewController.h; sourceTree = "<group>"; };
340FC88A204DAC8C007AEB0F /* NotificationSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationSettingsViewController.h; sourceTree = "<group>"; };
340FC88B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationSettingsOptionsViewController.h; sourceTree = "<group>"; };
340FC88E204DAC8C007AEB0F /* OWSBackupSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupSettingsViewController.m; sourceTree = "<group>"; };
340FC88F204DAC8C007AEB0F /* PrivacySettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrivacySettingsTableViewController.h; sourceTree = "<group>"; };
340FC894204DAC8C007AEB0F /* OWSSoundSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSoundSettingsViewController.h; sourceTree = "<group>"; };
340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQRCodeScanningViewController.m; sourceTree = "<group>"; };
@ -1002,11 +982,8 @@
34330AA11E79686200DF2FB9 /* OWSProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProgressView.h; sourceTree = "<group>"; };
34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = "<group>"; };
34386A53207D271C009F5D9C /* NeverClearView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeverClearView.swift; sourceTree = "<group>"; };
3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupRestoreViewController.swift; sourceTree = "<group>"; };
34480B371FD092A900BC14EF /* SignalShareExtension-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Bridging-Header.h"; sourceTree = "<group>"; };
34480B381FD092E300BC14EF /* SessionShareExtension-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SessionShareExtension-Prefix.pch"; sourceTree = "<group>"; };
344825C4211390C700DB4BD8 /* OWSOrphanDataCleaner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOrphanDataCleaner.h; sourceTree = "<group>"; };
344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOrphanDataCleaner.m; sourceTree = "<group>"; };
346129971FD1E4D900532771 /* SignalApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalApp.m; sourceTree = "<group>"; };
346129981FD1E4DA00532771 /* SignalApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalApp.h; sourceTree = "<group>"; };
34661FB720C1C0D60056EDD6 /* message_sent.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; name = message_sent.aiff; path = Session/Meta/AudioFiles/message_sent.aiff; sourceTree = SOURCE_ROOT; };
@ -1017,18 +994,6 @@
3496955A219B605E00DCFE74 /* PhotoCollectionPickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCollectionPickerController.swift; sourceTree = "<group>"; };
3496955B219B605E00DCFE74 /* PhotoLibrary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoLibrary.swift; sourceTree = "<group>"; };
3496955F21A2FC8100DCFE74 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
3496956221A301A100DCFE74 /* OWSBackupExportJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupExportJob.m; sourceTree = "<group>"; };
3496956321A301A100DCFE74 /* OWSBackupLazyRestore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSBackupLazyRestore.swift; sourceTree = "<group>"; };
3496956421A301A100DCFE74 /* OWSBackup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackup.h; sourceTree = "<group>"; };
3496956521A301A100DCFE74 /* OWSBackupIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupIO.m; sourceTree = "<group>"; };
3496956621A301A100DCFE74 /* OWSBackupImportJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupImportJob.m; sourceTree = "<group>"; };
3496956721A301A100DCFE74 /* OWSBackupJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupJob.h; sourceTree = "<group>"; };
3496956821A301A100DCFE74 /* OWSBackupExportJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupExportJob.h; sourceTree = "<group>"; };
3496956921A301A100DCFE74 /* OWSBackup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackup.m; sourceTree = "<group>"; };
3496956A21A301A100DCFE74 /* OWSBackupJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupJob.m; sourceTree = "<group>"; };
3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSBackupAPI.swift; sourceTree = "<group>"; };
3496956C21A301A100DCFE74 /* OWSBackupImportJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupImportJob.h; sourceTree = "<group>"; };
3496956D21A301A100DCFE74 /* OWSBackupIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupIO.h; sourceTree = "<group>"; };
34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSImagePickerController.swift; sourceTree = "<group>"; };
34A8B3502190A40E00218A25 /* MediaAlbumView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaAlbumView.swift; sourceTree = "<group>"; };
34ABC0E321DD20C500ED9469 /* ConversationMessageMapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationMessageMapping.swift; sourceTree = "<group>"; };
@ -1499,7 +1464,6 @@
C33FDB51255A580D00E217F9 /* NSUserDefaults+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUserDefaults+OWS.h"; sourceTree = "<group>"; };
C33FDB54255A580D00E217F9 /* DataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataSource.h; sourceTree = "<group>"; };
C33FDB56255A580D00E217F9 /* TSOutgoingMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSOutgoingMessage.m; sourceTree = "<group>"; };
C33FDB58255A580E00E217F9 /* OWSPrimaryStorage+Loki.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OWSPrimaryStorage+Loki.m"; sourceTree = "<group>"; };
C33FDB59255A580E00E217F9 /* OWSFailedAttachmentDownloadsJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSFailedAttachmentDownloadsJob.m; sourceTree = "<group>"; };
C33FDB5B255A580E00E217F9 /* YapDatabaseTransaction+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "YapDatabaseTransaction+OWS.m"; sourceTree = "<group>"; };
C33FDB5C255A580E00E217F9 /* NSArray+Functional.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Functional.h"; sourceTree = "<group>"; };
@ -1530,7 +1494,6 @@
C33FDB99255A581300E217F9 /* OWSPrimaryStorage+keyFromIntLong.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OWSPrimaryStorage+keyFromIntLong.m"; sourceTree = "<group>"; };
C33FDB9C255A581300E217F9 /* TSIncomingMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSIncomingMessage.h; sourceTree = "<group>"; };
C33FDB9E255A581400E217F9 /* TSAttachmentPointer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSAttachmentPointer.m; sourceTree = "<group>"; };
C33FDBA0255A581400E217F9 /* TSStorageHeaders.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSStorageHeaders.h; sourceTree = "<group>"; };
C33FDBA1255A581400E217F9 /* OWSOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOperation.h; sourceTree = "<group>"; };
C33FDBA4255A581400E217F9 /* OWSDisappearingMessagesConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDisappearingMessagesConfiguration.m; sourceTree = "<group>"; };
C33FDBA8255A581500E217F9 /* OWSLinkPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSLinkPreview.swift; sourceTree = "<group>"; };
@ -1543,7 +1506,6 @@
C33FDBB8255A581600E217F9 /* TSThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSThread.m; sourceTree = "<group>"; };
C33FDBB9255A581600E217F9 /* ProfileManagerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProfileManagerProtocol.h; sourceTree = "<group>"; };
C33FDBBA255A581600E217F9 /* OWSPrimaryStorage+keyFromIntLong.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OWSPrimaryStorage+keyFromIntLong.h"; sourceTree = "<group>"; };
C33FDBBB255A581600E217F9 /* OWSPrimaryStorage+Loki.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OWSPrimaryStorage+Loki.h"; sourceTree = "<group>"; };
C33FDBBC255A581600E217F9 /* SSKKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSKKeychainStorage.swift; sourceTree = "<group>"; };
C33FDBC1255A581700E217F9 /* General.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = "<group>"; };
C33FDBC2255A581700E217F9 /* SSKAsserts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKAsserts.h; sourceTree = "<group>"; };
@ -1556,7 +1518,6 @@
C33FDBE1255A581A00E217F9 /* LKGroupUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LKGroupUtilities.m; sourceTree = "<group>"; };
C33FDBE9255A581A00E217F9 /* TSInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSInteraction.m; sourceTree = "<group>"; };
C33FDBEC255A581B00E217F9 /* OWSRecipientIdentity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSRecipientIdentity.m; sourceTree = "<group>"; };
C33FDBEF255A581B00E217F9 /* TSStorageKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSStorageKeys.h; sourceTree = "<group>"; };
C33FDBF1255A581B00E217F9 /* OWSIdentityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSIdentityManager.h; sourceTree = "<group>"; };
C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLSessionDataTask+StatusCode.h"; sourceTree = "<group>"; };
C33FDBF8255A581C00E217F9 /* NSArray+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+OWS.h"; sourceTree = "<group>"; };
@ -1652,7 +1613,6 @@
C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSScreenLock.swift; path = "SignalUtilitiesKit/Screen Lock/OWSScreenLock.swift"; sourceTree = SOURCE_ROOT; };
C38EF2E3255B6DB9007E1867 /* OWSUnreadIndicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSUnreadIndicator.m; path = SignalUtilitiesKit/Messaging/OWSUnreadIndicator.m; sourceTree = SOURCE_ROOT; };
C38EF2E4255B6DB9007E1867 /* FullTextSearcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FullTextSearcher.swift; path = SignalUtilitiesKit/Messaging/FullTextSearcher.swift; sourceTree = SOURCE_ROOT; };
C38EF2E5255B6DB9007E1867 /* AppPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppPreferences.swift; path = SignalUtilitiesKit/Database/AppPreferences.swift; sourceTree = SOURCE_ROOT; };
C38EF2E9255B6DBA007E1867 /* OWSUnreadIndicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSUnreadIndicator.h; path = SignalUtilitiesKit/Messaging/OWSUnreadIndicator.h; sourceTree = SOURCE_ROOT; };
C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProximityMonitoringManager.swift; path = SessionMessagingKit/Utilities/ProximityMonitoringManager.swift; sourceTree = SOURCE_ROOT; };
C38EF2ED255B6DBB007E1867 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DisplayableText.swift; path = SignalUtilitiesKit/Utilities/DisplayableText.swift; sourceTree = SOURCE_ROOT; };
@ -1718,7 +1678,6 @@
C38EF3B7255B6DE6007E1867 /* ImageEditorCanvasView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageEditorCanvasView.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift"; sourceTree = SOURCE_ROOT; };
C38EF3D1255B6DEE007E1867 /* ThreadViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ThreadViewHelper.m; path = SignalUtilitiesKit/Database/ThreadViewHelper.m; sourceTree = SOURCE_ROOT; };
C38EF3D2255B6DEE007E1867 /* ThreadViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ThreadViewHelper.h; path = SignalUtilitiesKit/Database/ThreadViewHelper.h; sourceTree = SOURCE_ROOT; };
C38EF3D3255B6DEE007E1867 /* OWSSearchBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSSearchBar.h; path = "SignalUtilitiesKit/Shared Views/OWSSearchBar.h"; sourceTree = SOURCE_ROOT; };
C38EF3D4255B6DEE007E1867 /* DisappearingTimerConfigurationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DisappearingTimerConfigurationView.swift; path = SignalUtilitiesKit/Messaging/DisappearingTimerConfigurationView.swift; sourceTree = SOURCE_ROOT; };
C38EF3D6255B6DEF007E1867 /* ContactCellView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ContactCellView.m; path = "SignalUtilitiesKit/To Do/ContactCellView.m"; sourceTree = SOURCE_ROOT; };
C38EF3D7255B6DF0007E1867 /* OWSTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSTextField.h; path = "SignalUtilitiesKit/Shared Views/OWSTextField.h"; sourceTree = SOURCE_ROOT; };
@ -1737,7 +1696,6 @@
C38EF3E6255B6DF4007E1867 /* ContactTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ContactTableViewCell.h; path = "SignalUtilitiesKit/To Do/ContactTableViewCell.h"; sourceTree = SOURCE_ROOT; };
C38EF3E7255B6DF5007E1867 /* OWSButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSButton.swift; path = "SignalUtilitiesKit/Shared Views/OWSButton.swift"; sourceTree = SOURCE_ROOT; };
C38EF3E9255B6DF6007E1867 /* Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Toast.swift; path = "SignalUtilitiesKit/Shared Views/Toast.swift"; sourceTree = SOURCE_ROOT; };
C38EF3EA255B6DF6007E1867 /* OWSSearchBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSSearchBar.m; path = "SignalUtilitiesKit/Shared Views/OWSSearchBar.m"; sourceTree = SOURCE_ROOT; };
C38EF3EB255B6DF6007E1867 /* ContactTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ContactTableViewCell.m; path = "SignalUtilitiesKit/To Do/ContactTableViewCell.m"; sourceTree = SOURCE_ROOT; };
C38EF3EC255B6DF6007E1867 /* OWSFlatButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSFlatButton.swift; path = "SignalUtilitiesKit/Shared Views/OWSFlatButton.swift"; sourceTree = SOURCE_ROOT; };
C38EF3ED255B6DF6007E1867 /* TappableStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableStackView.swift; path = "SignalUtilitiesKit/Shared Views/TappableStackView.swift"; sourceTree = SOURCE_ROOT; };
@ -1818,7 +1776,6 @@
C3CA3ABD255CDB0D00F4C6D4 /* portuguese.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = portuguese.txt; sourceTree = "<group>"; };
C3CA3AC7255CDB2900F4C6D4 /* spanish.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = spanish.txt; sourceTree = "<group>"; };
C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundPoller.swift; sourceTree = "<group>"; };
C3D9E40B25676C100040E4F3 /* Storage+Conformances.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Conformances.swift"; sourceTree = "<group>"; };
C3D9E41E25676C870040E4F3 /* OWSPrimaryStorageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSPrimaryStorageProtocol.swift; sourceTree = "<group>"; };
C3D9E43025676D3D0040E4F3 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
C3DA9C0625AE7396008F7C7E /* ConfigurationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationMessage.swift; sourceTree = "<group>"; };
@ -2082,8 +2039,6 @@
4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */,
B90418E4183E9DD40038554A /* DateUtil.h */,
B90418E5183E9DD40038554A /* DateUtil.m */,
344825C4211390C700DB4BD8 /* OWSOrphanDataCleaner.h */,
344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */,
4C21D5D5223A9DC500EF8A77 /* UIAlerts+iOS9.m */,
45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */,
45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */,
@ -3067,28 +3022,6 @@
path = Notifications;
sourceTree = "<group>";
};
C36096BC25AD1C3E008B62B2 /* Backups */ = {
isa = PBXGroup;
children = (
340FC87F204DAC8C007AEB0F /* OWSBackupSettingsViewController.h */,
340FC88E204DAC8C007AEB0F /* OWSBackupSettingsViewController.m */,
3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */,
3496956421A301A100DCFE74 /* OWSBackup.h */,
3496956921A301A100DCFE74 /* OWSBackup.m */,
3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */,
3496956821A301A100DCFE74 /* OWSBackupExportJob.h */,
3496956221A301A100DCFE74 /* OWSBackupExportJob.m */,
3496956C21A301A100DCFE74 /* OWSBackupImportJob.h */,
3496956621A301A100DCFE74 /* OWSBackupImportJob.m */,
3496956D21A301A100DCFE74 /* OWSBackupIO.h */,
3496956521A301A100DCFE74 /* OWSBackupIO.m */,
3496956721A301A100DCFE74 /* OWSBackupJob.h */,
3496956A21A301A100DCFE74 /* OWSBackupJob.m */,
3496956321A301A100DCFE74 /* OWSBackupLazyRestore.swift */,
);
path = Backups;
sourceTree = "<group>";
};
C36096ED25AD20FD008B62B2 /* Media Viewing & Editing */ = {
isa = PBXGroup;
children = (
@ -3209,8 +3142,6 @@
C38EF3EC255B6DF6007E1867 /* OWSFlatButton.swift */,
C38EF3DB255B6DF1007E1867 /* OWSLayerView.swift */,
C38EF3D9255B6DF1007E1867 /* OWSNavigationBar.swift */,
C38EF3D3255B6DEE007E1867 /* OWSSearchBar.h */,
C38EF3EA255B6DF6007E1867 /* OWSSearchBar.m */,
C38EF3D7255B6DF0007E1867 /* OWSTextField.h */,
C38EF3E0255B6DF3007E1867 /* OWSTextField.m */,
C38EF3D8255B6DF0007E1867 /* OWSTextView.h */,
@ -3234,8 +3165,6 @@
C38EF3EB255B6DF6007E1867 /* ContactTableViewCell.m */,
C38EF2D2255B6DAF007E1867 /* OWSProfileManager.h */,
C38EF2CF255B6DAE007E1867 /* OWSProfileManager.m */,
C33FDBBB255A581600E217F9 /* OWSPrimaryStorage+Loki.h */,
C33FDB58255A580E00E217F9 /* OWSPrimaryStorage+Loki.m */,
);
path = "To Do";
sourceTree = "<group>";
@ -3264,14 +3193,10 @@
isa = PBXGroup;
children = (
C379DCE82567330E0002D4EB /* Migrations */,
C38EF2E5255B6DB9007E1867 /* AppPreferences.swift */,
C33FDBBA255A581600E217F9 /* OWSPrimaryStorage+keyFromIntLong.h */,
C33FDB99255A581300E217F9 /* OWSPrimaryStorage+keyFromIntLong.m */,
C3D9E40B25676C100040E4F3 /* Storage+Conformances.swift */,
C38EF3D2255B6DEE007E1867 /* ThreadViewHelper.h */,
C38EF3D1255B6DEE007E1867 /* ThreadViewHelper.m */,
C33FDBA0255A581400E217F9 /* TSStorageHeaders.h */,
C33FDBEF255A581B00E217F9 /* TSStorageKeys.h */,
C33FDA6D255A57FA00E217F9 /* YapDatabase+Promise.swift */,
);
path = Database;
@ -3486,8 +3411,6 @@
C38EF302255B6DBE007E1867 /* OWSAnyTouchGestureRecognizer.h */,
C38EF2F0255B6DBB007E1867 /* OWSAnyTouchGestureRecognizer.m */,
C38EF2ED255B6DBB007E1867 /* DisplayableText.swift */,
C38EF212255B6D3A007E1867 /* Theme.h */,
C38EF214255B6D3A007E1867 /* Theme.m */,
C38EF3DC255B6DF1007E1867 /* DirectionalPanGestureRecognizer.swift */,
C38EF240255B6D67007E1867 /* UIView+OWS.swift */,
C38EF236255B6D65007E1867 /* UIViewController+OWS.h */,
@ -3701,7 +3624,6 @@
isa = PBXGroup;
children = (
C3F0A58F255C8E3D007BE2A3 /* Meta */,
C36096BC25AD1C3E008B62B2 /* Backups */,
B8B558ED26C4B55F00693325 /* Calls */,
C360969C25AD18BA008B62B2 /* Closed Groups */,
B835246C25C38AA20089A44F /* Conversations */,
@ -3744,7 +3666,6 @@
C33FDD5B255A582000E217F9 /* OWSOperation.h in Headers */,
C38EF313255B6DBF007E1867 /* OWSUnreadIndicator.h in Headers */,
C33FDD7C255A582000E217F9 /* SSKAsserts.h in Headers */,
C33FDDA9255A582000E217F9 /* TSStorageKeys.h in Headers */,
C38EF3F6255B6DF7007E1867 /* OWSTextView.h in Headers */,
C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */,
C38EF32B255B6DBF007E1867 /* OWSFormat.h in Headers */,
@ -3755,12 +3676,9 @@
C38EF243255B6D67007E1867 /* UIViewController+OWS.h in Headers */,
C38EF35D255B6DCC007E1867 /* OWSNavigationController.h in Headers */,
C38EF249255B6D67007E1867 /* UIColor+OWS.h in Headers */,
C38EF216255B6D3B007E1867 /* Theme.h in Headers */,
C38EF3F0255B6DF7007E1867 /* ThreadViewHelper.h in Headers */,
C38EF274255B6D7A007E1867 /* OWSResaveCollectionDBMigration.h in Headers */,
C33FDC95255A582000E217F9 /* OWSFailedMessagesJob.h in Headers */,
C38EF3F1255B6DF7007E1867 /* OWSSearchBar.h in Headers */,
C33FDD75255A582000E217F9 /* OWSPrimaryStorage+Loki.h in Headers */,
C38EF277255B6D7A007E1867 /* OWSDatabaseMigration.h in Headers */,
C38EF3F5255B6DF7007E1867 /* OWSTextField.h in Headers */,
C38EF275255B6D7A007E1867 /* OWSDatabaseMigrationRunner.h in Headers */,
@ -3781,7 +3699,6 @@
C33FD9AF255A548A00E217F9 /* SignalUtilitiesKit.h in Headers */,
C33FDC50255A582000E217F9 /* OWSDispatch.h in Headers */,
C33FDD06255A582000E217F9 /* AppVersion.h in Headers */,
C33FDD5A255A582000E217F9 /* TSStorageHeaders.h in Headers */,
C33FDCA2255A582000E217F9 /* OWSMessageUtils.h in Headers */,
C38EF28F255B6D86007E1867 /* VersionMigrations.h in Headers */,
);
@ -4559,7 +4476,6 @@
C38EF3FD255B6DF7007E1867 /* OWSTextView.m in Sources */,
C38EF3C6255B6DE7007E1867 /* ImageEditorModel.swift in Sources */,
C38EF317255B6DBF007E1867 /* DisplayableText.swift in Sources */,
C38EF30F255B6DBF007E1867 /* AppPreferences.swift in Sources */,
C38EF3C3255B6DE7007E1867 /* ImageEditorTextItem.swift in Sources */,
7BD477AE27F526E3004E2822 /* BlockingManagerRemovalMigration.swift in Sources */,
C33FDC7D255A582000E217F9 /* OWSDispatch.m in Sources */,
@ -4569,8 +4485,6 @@
C38EF39B255B6DDA007E1867 /* ThreadViewModel.swift in Sources */,
C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */,
C38EF273255B6D7A007E1867 /* OWSDatabaseMigrationRunner.m in Sources */,
C33FDD12255A582000E217F9 /* OWSPrimaryStorage+Loki.m in Sources */,
C3D9E40C25676C100040E4F3 /* Storage+Conformances.swift in Sources */,
C38EF31A255B6DBF007E1867 /* OWSAnyTouchGestureRecognizer.m in Sources */,
C38EF385255B6DD2007E1867 /* AttachmentTextToolbar.swift in Sources */,
C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */,
@ -4613,7 +4527,6 @@
C38EF400255B6DF7007E1867 /* GalleryRailView.swift in Sources */,
C38EF32E255B6DBF007E1867 /* ImageCache.swift in Sources */,
C38EF32F255B6DBF007E1867 /* OWSFormat.m in Sources */,
C38EF218255B6D3B007E1867 /* Theme.m in Sources */,
C38EF3BA255B6DE7007E1867 /* ImageEditorItem.swift in Sources */,
C38EF3F7255B6DF7007E1867 /* OWSNavigationBar.swift in Sources */,
C38EF2D4255B6DAF007E1867 /* OWSProfileManager.m in Sources */,
@ -4627,7 +4540,6 @@
C33FDCFA255A582000E217F9 /* SignalIOSProto.swift in Sources */,
C33FDD13255A582000E217F9 /* OWSFailedAttachmentDownloadsJob.m in Sources */,
C38EF24D255B6D67007E1867 /* UIView+OWS.swift in Sources */,
C38EF408255B6DF7007E1867 /* OWSSearchBar.m in Sources */,
C38EF38B255B6DD2007E1867 /* AttachmentPrepViewController.swift in Sources */,
C33FDC7B255A582000E217F9 /* NSSet+Functional.m in Sources */,
C38EF405255B6DF7007E1867 /* OWSButton.swift in Sources */,
@ -4937,10 +4849,8 @@
B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */,
B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */,
452EC6DF205E9E30000E787C /* MediaGalleryViewController.swift in Sources */,
3496956E21A301A100DCFE74 /* OWSBackupExportJob.m in Sources */,
4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */,
34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */,
3496957421A301A100DCFE74 /* OWSBackupAPI.swift in Sources */,
C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */,
7BAF54D027ACCEEC003D12F8 /* EmptySearchResultCell.swift in Sources */,
B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */,
@ -4948,7 +4858,6 @@
B879D449247E1BE300DB3608 /* PathVC.swift in Sources */,
B877E24626CA13BA0007970A /* CallVC+Camera.swift in Sources */,
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */,
34D1F0871F8678AA0066283D /* ConversationViewItem.m in Sources */,
451A13B11E13DED2000A50FD /* AppNotifications.swift in Sources */,
34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */,
@ -4988,7 +4897,6 @@
B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */,
D221A09A169C9E5E00537ABF /* main.m in Sources */,
7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */,
3496957221A301A100DCFE74 /* OWSBackup.m in Sources */,
B835247925C38D880089A44F /* MessageCell.swift in Sources */,
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */,
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */,
@ -4998,7 +4906,6 @@
B82149B825D60393009C0F2A /* BlockedModal.swift in Sources */,
B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */,
346129991FD1E4DA00532771 /* SignalApp.m in Sources */,
3496957121A301A100DCFE74 /* OWSBackupImportJob.m in Sources */,
7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */,
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */,
C331FFFE2558FF3B00070591 /* ConversationCell.swift in Sources */,
@ -5019,7 +4926,6 @@
B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */,
4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */,
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */,
C328254925CA60E60062D0A7 /* ContextMenuVC+Action.swift in Sources */,
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
@ -5053,7 +4959,6 @@
C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */,
7B1581E827210ECC00848B49 /* RenderView.swift in Sources */,
7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */,
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */,
7BAADFCE27B215FE007BCF92 /* UIView+Draggable.swift in Sources */,
45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */,
4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */,
@ -5077,7 +4982,6 @@
34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */,
C31A6C5C247F2CF3001123EF /* CGRect+Utilities.swift in Sources */,
4C21D5D6223A9DC500EF8A77 /* UIAlerts+iOS9.m in Sources */,
3496957021A301A100DCFE74 /* OWSBackupIO.m in Sources */,
B8269D3325C7A8C600488AB4 /* InputViewButton.swift in Sources */,
B8269D3D25C7B34D00488AB4 /* InputTextView.swift in Sources */,
76EB054018170B33006006FC /* AppDelegate.m in Sources */,
@ -5100,7 +5004,6 @@
B835249B25C3AB650089A44F /* VisibleMessageCell.swift in Sources */,
340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */,
B8D0A25025E3678700C1835E /* LinkDeviceVC.swift in Sources */,
3496957321A301A100DCFE74 /* OWSBackupJob.m in Sources */,
B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */,
7B93D06D27CF175800811CB6 /* MessageRequestsCell.swift in Sources */,
7B93D07727CF1A8A00811CB6 /* MockDataGenerator.swift in Sources */,
@ -5118,7 +5021,6 @@
C33100092558FF6D00070591 /* UserCell.swift in Sources */,
B8269D2925C7A4B400488AB4 /* InputView.swift in Sources */,
C374EEE225DA26740073A857 /* LinkPreviewModal.swift in Sources */,
3496956F21A301A100DCFE74 /* OWSBackupLazyRestore.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -1,175 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc
public class BackupRestoreViewController: OWSTableViewController {
private var hasBegunImport = false
// MARK: - Dependencies
private var backup: OWSBackup {
return AppEnvironment.shared.backup
}
// MARK: -
override public func loadView() {
super.loadView()
navigationItem.title = NSLocalizedString("SETTINGS_BACKUP", comment: "Label for the backup view in app settings.")
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(didPressCancelButton))
}
override public func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(backupStateDidChange),
name: NSNotification.Name(NSNotificationNameBackupStateDidChange),
object: nil)
updateTableContents()
}
private func updateTableContents() {
if hasBegunImport {
updateProgressContents()
} else {
updateDecisionContents()
}
}
private func updateDecisionContents() {
let contents = OWSTableContents()
let section = OWSTableSection()
section.headerTitle = NSLocalizedString("BACKUP_RESTORE_DECISION_TITLE", comment: "Label for the backup restore decision section.")
section.add(OWSTableItem.actionItem(withText: NSLocalizedString("CHECK_FOR_BACKUP_DO_NOT_RESTORE",
comment: "The label for the 'do not restore backup' button."), actionBlock: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.cancelAndDismiss()
}))
section.add(OWSTableItem.actionItem(withText: NSLocalizedString("CHECK_FOR_BACKUP_RESTORE",
comment: "The label for the 'restore backup' button."), actionBlock: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.startImport()
}))
contents.addSection(section)
self.contents = contents
}
private var progressFormatter: NumberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .percent
numberFormatter.maximumFractionDigits = 0
numberFormatter.multiplier = 1
return numberFormatter
}()
private func updateProgressContents() {
let contents = OWSTableContents()
let section = OWSTableSection()
section.add(OWSTableItem.label(withText: NSLocalizedString("BACKUP_RESTORE_STATUS", comment: "Label for the backup restore status."), accessoryText: NSStringForBackupImportState(backup.backupImportState)))
if backup.backupImportState == .inProgress {
if let backupImportDescription = backup.backupImportDescription {
section.add(OWSTableItem.label(withText: NSLocalizedString("BACKUP_RESTORE_DESCRIPTION", comment: "Label for the backup restore description."), accessoryText: backupImportDescription))
}
if let backupImportProgress = backup.backupImportProgress {
let progressInt = backupImportProgress.floatValue * 100
if let progressString = progressFormatter.string(from: NSNumber(value: progressInt)) {
section.add(OWSTableItem.label(withText: NSLocalizedString("BACKUP_RESTORE_PROGRESS", comment: "Label for the backup restore progress."), accessoryText: progressString))
} else {
owsFailDebug("Could not format progress: \(progressInt)")
}
}
}
contents.addSection(section)
self.contents = contents
// TODO: Add cancel button.
}
// MARK: Helpers
@objc
private func didPressCancelButton(sender: UIButton) {
Logger.info("")
// TODO: Cancel import.
cancelAndDismiss()
}
@objc
private func cancelAndDismiss() {
Logger.info("")
backup.setHasPendingRestoreDecision(false)
showHomeView()
}
@objc
private func startImport() {
Logger.info("")
hasBegunImport = true
backup.tryToImport()
}
private func showHomeView() {
// In production, this view will never be presented in a modal.
// During testing (debug UI, etc.), it may be a modal.
let isModal = navigationController?.presentingViewController != nil
if isModal {
dismiss(animated: true, completion: {
SignalApp.shared().showHomeView()
})
} else {
SignalApp.shared().showHomeView()
}
NotificationCenter.default.removeObserver(self)
}
// MARK: - Notifications
@objc func backupStateDidChange() {
AssertIsOnMainThread()
Logger.verbose("backup.backupImportState: \(NSStringForBackupImportState(backup.backupImportState))")
Logger.flush()
if backup.backupImportState == .succeeded {
backup.setHasPendingRestoreDecision(false)
showHomeView()
} else {
updateTableContents()
}
}
// MARK: Orientation
public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
}

View File

@ -1,105 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
extern NSString *const NSNotificationNameBackupStateDidChange;
typedef void (^OWSBackupBoolBlock)(BOOL value);
typedef void (^OWSBackupStringListBlock)(NSArray<NSString *> *value);
typedef void (^OWSBackupErrorBlock)(NSError *error);
typedef NS_ENUM(NSUInteger, OWSBackupState) {
// Has never backed up, not trying to backup yet.
OWSBackupState_Idle = 0,
// Backing up.
OWSBackupState_InProgress,
// Last backup failed.
OWSBackupState_Failed,
// Last backup succeeded.
OWSBackupState_Succeeded,
};
NSString *NSStringForBackupExportState(OWSBackupState state);
NSString *NSStringForBackupImportState(OWSBackupState state);
NSArray<NSString *> *MiscCollectionsToBackup(void);
NSError *OWSBackupErrorWithDescription(NSString *description);
@class AnyPromise;
@class OWSBackupIO;
@class TSAttachmentPointer;
@class TSThread;
@class YapDatabaseConnection;
@interface OWSBackup : NSObject
- (instancetype)init NS_DESIGNATED_INITIALIZER;
+ (instancetype)sharedManager NS_SWIFT_NAME(shared());
#pragma mark - Backup Export
@property (atomic, readonly) OWSBackupState backupExportState;
// If a "backup export" is in progress (see backupExportState),
// backupExportDescription _might_ contain a string that describes
// the current phase and backupExportProgress _might_ contain a
// 0.0<=x<=1.0 progress value that indicates progress within the
// current phase.
@property (nonatomic, readonly, nullable) NSString *backupExportDescription;
@property (nonatomic, readonly, nullable) NSNumber *backupExportProgress;
+ (BOOL)isFeatureEnabled;
- (BOOL)isBackupEnabled;
- (void)setIsBackupEnabled:(BOOL)value;
- (BOOL)hasPendingRestoreDecision;
- (void)setHasPendingRestoreDecision:(BOOL)value;
- (void)tryToExportBackup;
- (void)cancelExportBackup;
#pragma mark - Backup Import
@property (atomic, readonly) OWSBackupState backupImportState;
// If a "backup import" is in progress (see backupImportState),
// backupImportDescription _might_ contain a string that describes
// the current phase and backupImportProgress _might_ contain a
// 0.0<=x<=1.0 progress value that indicates progress within the
// current phase.
@property (nonatomic, readonly, nullable) NSString *backupImportDescription;
@property (nonatomic, readonly, nullable) NSNumber *backupImportProgress;
- (void)allRecipientIdsWithManifestsInCloud:(OWSBackupStringListBlock)success failure:(OWSBackupErrorBlock)failure;
- (AnyPromise *)ensureCloudKitAccess __attribute__((warn_unused_result));
- (void)checkCanImportBackup:(OWSBackupBoolBlock)success failure:(OWSBackupErrorBlock)failure;
// TODO: After a successful import, we should enable backup and
// preserve our PIN and/or private key so that restored users
// continues to backup.
- (void)tryToImportBackup;
- (void)cancelImportBackup;
- (void)logBackupRecords;
- (void)clearAllCloudKitRecords;
- (void)logBackupMetadataCache:(YapDatabaseConnection *)dbConnection;
#pragma mark - Lazy Restore
- (NSArray<NSString *> *)attachmentRecordNamesForLazyRestore;
- (NSArray<NSString *> *)attachmentIdsForLazyRestore;
- (AnyPromise *)lazyRestoreAttachment:(TSAttachmentPointer *)attachment backupIO:(OWSBackupIO *)backupIO __attribute__((warn_unused_result));
@end
NS_ASSUME_NONNULL_END

View File

@ -1,911 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSBackup.h"
#import "OWSBackupExportJob.h"
#import "OWSBackupIO.h"
#import "OWSBackupImportJob.h"
#import "Session-Swift.h"
#import <PromiseKit/AnyPromise.h>
#import <SignalCoreKit/Randomness.h>
#import <SessionMessagingKit/OWSIdentityManager.h>
#import <SessionMessagingKit/YapDatabaseConnection+OWS.h>
@import CloudKit;
NS_ASSUME_NONNULL_BEGIN
NSString *const NSNotificationNameBackupStateDidChange = @"NSNotificationNameBackupStateDidChange";
NSString *const OWSPrimaryStorage_OWSBackupCollection = @"OWSPrimaryStorage_OWSBackupCollection";
NSString *const OWSBackup_IsBackupEnabledKey = @"OWSBackup_IsBackupEnabledKey";
NSString *const OWSBackup_LastExportSuccessDateKey = @"OWSBackup_LastExportSuccessDateKey";
NSString *const OWSBackup_LastExportFailureDateKey = @"OWSBackup_LastExportFailureDateKey";
NSString *const OWSBackupErrorDomain = @"OWSBackupErrorDomain";
NSString *NSStringForBackupExportState(OWSBackupState state)
{
switch (state) {
case OWSBackupState_Idle:
return NSLocalizedString(@"SETTINGS_BACKUP_STATUS_IDLE", @"Indicates that app is not backing up.");
case OWSBackupState_InProgress:
return NSLocalizedString(@"SETTINGS_BACKUP_STATUS_IN_PROGRESS", @"Indicates that app is backing up.");
case OWSBackupState_Failed:
return NSLocalizedString(@"SETTINGS_BACKUP_STATUS_FAILED", @"Indicates that the last backup failed.");
case OWSBackupState_Succeeded:
return NSLocalizedString(@"SETTINGS_BACKUP_STATUS_SUCCEEDED", @"Indicates that the last backup succeeded.");
}
}
NSString *NSStringForBackupImportState(OWSBackupState state)
{
switch (state) {
case OWSBackupState_Idle:
return NSLocalizedString(@"SETTINGS_BACKUP_IMPORT_STATUS_IDLE", @"Indicates that app is not restoring up.");
case OWSBackupState_InProgress:
return NSLocalizedString(
@"SETTINGS_BACKUP_IMPORT_STATUS_IN_PROGRESS", @"Indicates that app is restoring up.");
case OWSBackupState_Failed:
return NSLocalizedString(
@"SETTINGS_BACKUP_IMPORT_STATUS_FAILED", @"Indicates that the last backup restore failed.");
case OWSBackupState_Succeeded:
return NSLocalizedString(
@"SETTINGS_BACKUP_IMPORT_STATUS_SUCCEEDED", @"Indicates that the last backup restore succeeded.");
}
}
NSArray<NSString *> *MiscCollectionsToBackup(void)
{
return @[
OWSUserProfile.collection,
SSKIncrementingIdFinder.collectionName,
OWSPreferencesSignalDatabaseCollection,
];
}
typedef NS_ENUM(NSInteger, OWSBackupErrorCode) {
OWSBackupErrorCodeAssertionFailure = 0,
};
NSError *OWSBackupErrorWithDescription(NSString *description)
{
return [NSError errorWithDomain:@"OWSBackupErrorDomain"
code:OWSBackupErrorCodeAssertionFailure
userInfo:@{ NSLocalizedDescriptionKey : description }];
}
// TODO: Observe Reachability.
@interface OWSBackup () <OWSBackupJobDelegate>
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
// This property should only be accessed on the main thread.
@property (nonatomic, nullable) OWSBackupExportJob *backupExportJob;
// This property should only be accessed on the main thread.
@property (nonatomic, nullable) OWSBackupImportJob *backupImportJob;
@property (nonatomic, nullable) NSString *backupExportDescription;
@property (nonatomic, nullable) NSNumber *backupExportProgress;
@property (nonatomic, nullable) NSString *backupImportDescription;
@property (nonatomic, nullable) NSNumber *backupImportProgress;
@property (atomic) OWSBackupState backupExportState;
@property (atomic) OWSBackupState backupImportState;
@end
#pragma mark -
@implementation OWSBackup
@synthesize dbConnection = _dbConnection;
+ (instancetype)sharedManager
{
OWSAssertDebug(AppEnvironment.shared.backup);
return AppEnvironment.shared.backup;
}
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
self.backupExportState = OWSBackupState_Idle;
self.backupImportState = OWSBackupState_Idle;
OWSSingletonAssert();
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[self setup];
}];
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setup
{
if (!OWSBackup.isFeatureEnabled) {
return;
}
[OWSBackupAPI setup];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive:)
name:OWSApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(registrationStateDidChange)
name:RegistrationStateDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(ckAccountChanged)
name:CKAccountChangedNotification
object:nil];
// We want to start a backup if necessary on app launch, but app launch is a
// busy time and it's important to remain responsive, so wait a few seconds before
// starting the backup.
//
// TODO: Make this period longer.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self ensureBackupExportState];
});
}
- (YapDatabaseConnection *)dbConnection
{
@synchronized(self) {
if (!_dbConnection) {
_dbConnection = self.primaryStorage.newDatabaseConnection;
}
return _dbConnection;
}
}
#pragma mark - Dependencies
- (OWSPrimaryStorage *)primaryStorage
{
OWSAssertDebug(SSKEnvironment.shared.primaryStorage);
return SSKEnvironment.shared.primaryStorage;
}
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
+ (BOOL)isFeatureEnabled
{
return NO;
}
#pragma mark - Backup Export
- (void)tryToExportBackup
{
OWSAssertIsOnMainThread();
OWSAssertDebug(!self.backupExportJob);
if (!self.canBackupExport) {
// TODO: Offer a reason in the UI.
return;
}
if (!self.tsAccountManager.isRegisteredAndReady) {
OWSFailDebug(@"Can't backup; not registered and ready.");
return;
}
NSString *_Nullable recipientId = self.tsAccountManager.localNumber;
if (recipientId.length < 1) {
OWSFailDebug(@"Can't backup; missing recipientId.");
return;
}
// In development, make sure there's no export or import in progress.
[self.backupExportJob cancel];
self.backupExportJob = nil;
[self.backupImportJob cancel];
self.backupImportJob = nil;
self.backupExportState = OWSBackupState_InProgress;
self.backupExportJob = [[OWSBackupExportJob alloc] initWithDelegate:self recipientId:recipientId];
[self.backupExportJob start];
[self postDidChangeNotification];
}
- (void)cancelExportBackup
{
[self.backupExportJob cancel];
self.backupExportJob = nil;
[self ensureBackupExportState];
}
- (void)setLastExportSuccessDate:(NSDate *)value
{
OWSAssertDebug(value);
[self.dbConnection setDate:value
forKey:OWSBackup_LastExportSuccessDateKey
inCollection:OWSPrimaryStorage_OWSBackupCollection];
}
- (nullable NSDate *)lastExportSuccessDate
{
return [self.dbConnection dateForKey:OWSBackup_LastExportSuccessDateKey
inCollection:OWSPrimaryStorage_OWSBackupCollection];
}
- (void)setLastExportFailureDate:(NSDate *)value
{
OWSAssertDebug(value);
[self.dbConnection setDate:value
forKey:OWSBackup_LastExportFailureDateKey
inCollection:OWSPrimaryStorage_OWSBackupCollection];
}
- (nullable NSDate *)lastExportFailureDate
{
return [self.dbConnection dateForKey:OWSBackup_LastExportFailureDateKey
inCollection:OWSPrimaryStorage_OWSBackupCollection];
}
- (BOOL)isBackupEnabled
{
return [self.dbConnection boolForKey:OWSBackup_IsBackupEnabledKey
inCollection:OWSPrimaryStorage_OWSBackupCollection
defaultValue:NO];
}
- (void)setIsBackupEnabled:(BOOL)value
{
[self.dbConnection setBool:value
forKey:OWSBackup_IsBackupEnabledKey
inCollection:OWSPrimaryStorage_OWSBackupCollection];
if (!value) {
[self.dbConnection removeObjectForKey:OWSBackup_LastExportSuccessDateKey
inCollection:OWSPrimaryStorage_OWSBackupCollection];
[self.dbConnection removeObjectForKey:OWSBackup_LastExportFailureDateKey
inCollection:OWSPrimaryStorage_OWSBackupCollection];
}
[self postDidChangeNotification];
[self ensureBackupExportState];
}
- (BOOL)hasPendingRestoreDecision
{
return [self.tsAccountManager hasPendingBackupRestoreDecision];
}
- (void)setHasPendingRestoreDecision:(BOOL)value
{
[self.tsAccountManager setHasPendingBackupRestoreDecision:value];
}
- (BOOL)canBackupExport
{
if (!self.isBackupEnabled) {
return NO;
}
if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive) {
// Don't start backups when app is in the background.
return NO;
}
if (![self.tsAccountManager isRegisteredAndReady]) {
return NO;
}
return YES;
}
- (BOOL)shouldHaveBackupExport
{
if (!self.canBackupExport) {
return NO;
}
if (self.backupExportJob) {
// If there's already a job in progress, let it complete.
return YES;
}
NSDate *_Nullable lastExportSuccessDate = self.lastExportSuccessDate;
NSDate *_Nullable lastExportFailureDate = self.lastExportFailureDate;
// Wait N hours before retrying after a success.
const NSTimeInterval kRetryAfterSuccess = 24 * kHourInterval;
if (lastExportSuccessDate && fabs(lastExportSuccessDate.timeIntervalSinceNow) < kRetryAfterSuccess) {
return NO;
}
// Wait N hours before retrying after a failure.
const NSTimeInterval kRetryAfterFailure = 6 * kHourInterval;
if (lastExportFailureDate && fabs(lastExportFailureDate.timeIntervalSinceNow) < kRetryAfterFailure) {
return NO;
}
// Don't export backup if there's an import in progress.
//
// This conflict shouldn't occur in production since we won't enable backup
// export until an import is complete, but this could happen in development.
if (self.backupImportJob) {
return NO;
}
// TODO: There's other conditions that affect this decision,
// e.g. Reachability, wifi v. cellular, etc.
return YES;
}
- (void)ensureBackupExportState
{
OWSAssertIsOnMainThread();
if (!OWSBackup.isFeatureEnabled) {
return;
}
if (!CurrentAppContext().isMainApp) {
return;
}
if (!self.tsAccountManager.isRegisteredAndReady) {
OWSLogError(@"Can't backup; not registered and ready.");
return;
}
NSString *_Nullable recipientId = self.tsAccountManager.localNumber;
if (recipientId.length < 1) {
OWSFailDebug(@"Can't backup; missing recipientId.");
return;
}
// Start or abort a backup export if neccessary.
if (!self.shouldHaveBackupExport && self.backupExportJob) {
[self.backupExportJob cancel];
self.backupExportJob = nil;
} else if (self.shouldHaveBackupExport && !self.backupExportJob) {
self.backupExportJob = [[OWSBackupExportJob alloc] initWithDelegate:self recipientId:recipientId];
[self.backupExportJob start];
}
// Update the state flag.
OWSBackupState backupExportState = OWSBackupState_Idle;
if (self.backupExportJob) {
backupExportState = OWSBackupState_InProgress;
} else {
NSDate *_Nullable lastExportSuccessDate = self.lastExportSuccessDate;
NSDate *_Nullable lastExportFailureDate = self.lastExportFailureDate;
if (!lastExportSuccessDate && !lastExportFailureDate) {
backupExportState = OWSBackupState_Idle;
} else if (lastExportSuccessDate && lastExportFailureDate) {
backupExportState = ([lastExportSuccessDate isAfterDate:lastExportFailureDate] ? OWSBackupState_Succeeded
: OWSBackupState_Failed);
} else if (lastExportSuccessDate) {
backupExportState = OWSBackupState_Succeeded;
} else if (lastExportFailureDate) {
backupExportState = OWSBackupState_Failed;
} else {
OWSFailDebug(@"unexpected condition.");
}
}
BOOL stateDidChange = self.backupExportState != backupExportState;
self.backupExportState = backupExportState;
if (stateDidChange) {
[self postDidChangeNotification];
}
}
#pragma mark - Backup Import
- (void)allRecipientIdsWithManifestsInCloud:(OWSBackupStringListBlock)success failure:(OWSBackupErrorBlock)failure
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
[OWSBackupAPI
allRecipientIdsWithManifestsInCloudWithSuccess:^(NSArray<NSString *> *recipientIds) {
dispatch_async(dispatch_get_main_queue(), ^{
success(recipientIds);
});
}
failure:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
failure(error);
});
}];
}
- (AnyPromise *)ensureCloudKitAccess
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
AnyPromise * (^failWithUnexpectedError)(void) = ^{
NSError *error = [NSError errorWithDomain:OWSBackupErrorDomain
code:1
userInfo:@{
NSLocalizedDescriptionKey : NSLocalizedString(@"BACKUP_UNEXPECTED_ERROR",
@"Error shown when backup fails due to an unexpected error.")
}];
return [AnyPromise promiseWithValue:error];
};
if (!self.tsAccountManager.isRegisteredAndReady) {
OWSLogError(@"Can't backup; not registered and ready.");
return failWithUnexpectedError();
}
NSString *_Nullable recipientId = self.tsAccountManager.localNumber;
if (recipientId.length < 1) {
OWSFailDebug(@"Can't backup; missing recipientId.");
return failWithUnexpectedError();
}
return [OWSBackupAPI ensureCloudKitAccessObjc];
}
- (void)checkCanImportBackup:(OWSBackupBoolBlock)success failure:(OWSBackupErrorBlock)failure
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
if (!OWSBackup.isFeatureEnabled) {
dispatch_async(dispatch_get_main_queue(), ^{
success(NO);
});
return;
}
void (^failWithUnexpectedError)(void) = ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error =
[NSError errorWithDomain:OWSBackupErrorDomain
code:1
userInfo:@{
NSLocalizedDescriptionKey : NSLocalizedString(@"BACKUP_UNEXPECTED_ERROR",
@"Error shown when backup fails due to an unexpected error.")
}];
failure(error);
});
};
if (!self.tsAccountManager.isRegisteredAndReady) {
OWSLogError(@"Can't backup; not registered and ready.");
return failWithUnexpectedError();
}
NSString *_Nullable recipientId = self.tsAccountManager.localNumber;
if (recipientId.length < 1) {
OWSFailDebug(@"Can't backup; missing recipientId.");
return failWithUnexpectedError();
}
[[OWSBackupAPI ensureCloudKitAccessObjc]
.thenInBackground(^{
return [OWSBackupAPI checkForManifestInCloudObjcWithRecipientId:recipientId];
})
.then(^(NSNumber *value) {
success(value.boolValue);
})
.catch(^(NSError *error) {
failure(error);
}) retainUntilComplete];
}
- (void)tryToImportBackup
{
OWSAssertIsOnMainThread();
OWSAssertDebug(!self.backupImportJob);
if (!self.tsAccountManager.isRegisteredAndReady) {
OWSLogError(@"Can't restore backup; not registered and ready.");
return;
}
NSString *_Nullable recipientId = self.tsAccountManager.localNumber;
if (recipientId.length < 1) {
OWSLogError(@"Can't restore backup; missing recipientId.");
return;
}
// In development, make sure there's no export or import in progress.
[self.backupExportJob cancel];
self.backupExportJob = nil;
[self.backupImportJob cancel];
self.backupImportJob = nil;
self.backupImportState = OWSBackupState_InProgress;
self.backupImportJob = [[OWSBackupImportJob alloc] initWithDelegate:self recipientId:recipientId];
[self.backupImportJob start];
[self postDidChangeNotification];
}
- (void)cancelImportBackup
{
[self.backupImportJob cancel];
self.backupImportJob = nil;
self.backupImportState = OWSBackupState_Idle;
[self postDidChangeNotification];
}
#pragma mark -
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
[self ensureBackupExportState];
}
- (void)registrationStateDidChange
{
OWSAssertIsOnMainThread();
[self ensureBackupExportState];
[self postDidChangeNotification];
}
- (void)ckAccountChanged
{
dispatch_async(dispatch_get_main_queue(), ^{
[self ensureBackupExportState];
[self postDidChangeNotification];
});
}
#pragma mark - OWSBackupJobDelegate
// We use a delegate method to avoid storing this key in memory.
- (nullable NSData *)backupEncryptionKey
{
// TODO: Use actual encryption key.
return [@"temp" dataUsingEncoding:NSUTF8StringEncoding];
}
- (void)backupJobDidSucceed:(OWSBackupJob *)backupJob
{
OWSAssertIsOnMainThread();
OWSLogInfo(@".");
if (self.backupImportJob == backupJob) {
self.backupImportJob = nil;
self.backupImportState = OWSBackupState_Succeeded;
} else if (self.backupExportJob == backupJob) {
self.backupExportJob = nil;
[self setLastExportSuccessDate:[NSDate new]];
[self ensureBackupExportState];
} else {
OWSLogWarn(@"obsolete job succeeded: %@", [backupJob class]);
return;
}
[self postDidChangeNotification];
}
- (void)backupJobDidFail:(OWSBackupJob *)backupJob error:(NSError *)error
{
OWSAssertIsOnMainThread();
OWSLogInfo(@": %@", error);
if (self.backupImportJob == backupJob) {
self.backupImportJob = nil;
self.backupImportState = OWSBackupState_Failed;
} else if (self.backupExportJob == backupJob) {
self.backupExportJob = nil;
[self setLastExportFailureDate:[NSDate new]];
[self ensureBackupExportState];
} else {
OWSLogInfo(@"obsolete backup job failed.");
return;
}
[self postDidChangeNotification];
}
- (void)backupJobDidUpdate:(OWSBackupJob *)backupJob
description:(nullable NSString *)description
progress:(nullable NSNumber *)progress
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
// TODO: Should we consolidate this state?
BOOL didChange;
if (self.backupImportJob == backupJob) {
didChange = !([NSObject isNullableObject:self.backupImportDescription equalTo:description] &&
[NSObject isNullableObject:self.backupImportProgress equalTo:progress]);
self.backupImportDescription = description;
self.backupImportProgress = progress;
} else if (self.backupExportJob == backupJob) {
didChange = !([NSObject isNullableObject:self.backupExportDescription equalTo:description] &&
[NSObject isNullableObject:self.backupExportProgress equalTo:progress]);
self.backupExportDescription = description;
self.backupExportProgress = progress;
} else {
return;
}
if (didChange) {
[self postDidChangeNotification];
}
}
- (void)logBackupRecords
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
if (!self.tsAccountManager.isRegisteredAndReady) {
OWSLogError(@"Can't interact with backup; not registered and ready.");
return;
}
NSString *_Nullable recipientId = self.tsAccountManager.localNumber;
if (recipientId.length < 1) {
OWSLogError(@"Can't interact with backup; missing recipientId.");
return;
}
[OWSBackupAPI fetchAllRecordNamesWithRecipientId:recipientId
success:^(NSArray<NSString *> *recordNames) {
for (NSString *recordName in [recordNames sortedArrayUsingSelector:@selector(compare:)]) {
OWSLogInfo(@"\t %@", recordName);
}
OWSLogInfo(@"record count: %zd", recordNames.count);
}
failure:^(NSError *error) {
OWSLogError(@"Failed to retrieve backup records: %@", error);
}];
}
- (void)clearAllCloudKitRecords
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
if (!self.tsAccountManager.isRegisteredAndReady) {
OWSLogError(@"Can't interact with backup; not registered and ready.");
return;
}
NSString *_Nullable recipientId = self.tsAccountManager.localNumber;
if (recipientId.length < 1) {
OWSLogError(@"Can't interact with backup; missing recipientId.");
return;
}
[OWSBackupAPI fetchAllRecordNamesWithRecipientId:recipientId
success:^(NSArray<NSString *> *recordNames) {
if (recordNames.count < 1) {
OWSLogInfo(@"No CloudKit records found to clear.");
return;
}
[OWSBackupAPI deleteRecordsFromCloudWithRecordNames:recordNames
success:^{
OWSLogInfo(@"Clear all CloudKit records succeeded.");
}
failure:^(NSError *error) {
OWSLogError(@"Clear all CloudKit records failed: %@.", error);
}];
}
failure:^(NSError *error) {
OWSLogError(@"Failed to retrieve CloudKit records: %@", error);
}];
}
#pragma mark - Lazy Restore
- (NSArray<NSString *> *)attachmentRecordNamesForLazyRestore
{
NSMutableArray<NSString *> *recordNames = [NSMutableArray new];
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
id ext = [transaction ext:TSLazyRestoreAttachmentsDatabaseViewExtensionName];
if (!ext) {
OWSFailDebug(@"Could not load database view.");
return;
}
[ext enumerateKeysAndObjectsInGroup:TSLazyRestoreAttachmentsGroup
usingBlock:^(
NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) {
if (![object isKindOfClass:[TSAttachmentPointer class]]) {
OWSFailDebug(
@"Unexpected object: %@ in collection:%@", [object class], collection);
return;
}
TSAttachmentPointer *attachmentPointer = object;
if (!attachmentPointer.lazyRestoreFragment) {
OWSFailDebug(
@"Invalid object: %@ in collection:%@", [object class], collection);
return;
}
[recordNames addObject:attachmentPointer.lazyRestoreFragment.recordName];
}];
}];
return recordNames;
}
- (NSArray<NSString *> *)attachmentIdsForLazyRestore
{
NSMutableArray<NSString *> *attachmentIds = [NSMutableArray new];
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
id ext = [transaction ext:TSLazyRestoreAttachmentsDatabaseViewExtensionName];
if (!ext) {
OWSFailDebug(@"Could not load database view.");
return;
}
[ext enumerateKeysInGroup:TSLazyRestoreAttachmentsGroup
usingBlock:^(NSString *collection, NSString *key, NSUInteger index, BOOL *stop) {
[attachmentIds addObject:key];
}];
}];
return attachmentIds;
}
- (AnyPromise *)lazyRestoreAttachment:(TSAttachmentPointer *)attachment backupIO:(OWSBackupIO *)backupIO
{
OWSAssertDebug(attachment);
OWSAssertDebug(backupIO);
OWSBackupFragment *_Nullable lazyRestoreFragment = attachment.lazyRestoreFragment;
if (!lazyRestoreFragment) {
OWSLogError(@"Attachment missing lazy restore metadata.");
return
[AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Attachment missing lazy restore metadata.")];
}
if (lazyRestoreFragment.recordName.length < 1 || lazyRestoreFragment.encryptionKey.length < 1) {
OWSLogError(@"Incomplete lazy restore metadata.");
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Incomplete lazy restore metadata.")];
}
// Use a predictable file path so that multiple "import backup" attempts
// will leverage successful file downloads from previous attempts.
//
// TODO: This will also require imports using a predictable jobTempDirPath.
NSString *tempFilePath = [backupIO generateTempFilePath];
return [OWSBackupAPI downloadFileFromCloudObjcWithRecordName:lazyRestoreFragment.recordName
toFileUrl:[NSURL fileURLWithPath:tempFilePath]]
.thenInBackground(^{
return [self lazyRestoreAttachment:attachment
backupIO:backupIO
encryptedFilePath:tempFilePath
encryptionKey:lazyRestoreFragment.encryptionKey];
});
}
- (AnyPromise *)lazyRestoreAttachment:(TSAttachmentPointer *)attachmentPointer
backupIO:(OWSBackupIO *)backupIO
encryptedFilePath:(NSString *)encryptedFilePath
encryptionKey:(NSData *)encryptionKey
{
OWSAssertDebug(attachmentPointer);
OWSAssertDebug(backupIO);
OWSAssertDebug(encryptedFilePath.length > 0);
OWSAssertDebug(encryptionKey.length > 0);
NSData *_Nullable data = [NSData dataWithContentsOfFile:encryptedFilePath];
if (!data) {
OWSLogError(@"Could not load encrypted file.");
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Could not load encrypted file.")];
}
NSString *decryptedFilePath = [backupIO generateTempFilePath];
@autoreleasepool {
if (![backupIO decryptFileAsFile:encryptedFilePath dstFilePath:decryptedFilePath encryptionKey:encryptionKey]) {
OWSLogError(@"Could not load decrypt file.");
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Could not load decrypt file.")];
}
}
TSAttachmentStream *stream = [[TSAttachmentStream alloc] initWithPointer:attachmentPointer];
NSString *attachmentFilePath = stream.originalFilePath;
if (attachmentFilePath.length < 1) {
OWSLogError(@"Attachment has invalid file path.");
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Attachment has invalid file path.")];
}
NSString *attachmentDirPath = [attachmentFilePath stringByDeletingLastPathComponent];
if (![OWSFileSystem ensureDirectoryExists:attachmentDirPath]) {
OWSLogError(@"Couldn't create directory for attachment file.");
return [AnyPromise
promiseWithValue:OWSBackupErrorWithDescription(@"Couldn't create directory for attachment file.")];
}
if (![OWSFileSystem deleteFileIfExists:attachmentFilePath]) {
OWSFailDebug(@"Couldn't delete existing file at attachment path.");
return [AnyPromise
promiseWithValue:OWSBackupErrorWithDescription(@"Couldn't delete existing file at attachment path.")];
}
NSError *error;
BOOL success =
[NSFileManager.defaultManager moveItemAtPath:decryptedFilePath toPath:attachmentFilePath error:&error];
if (!success || error) {
OWSLogError(@"Attachment file could not be restored: %@.", error);
return [AnyPromise promiseWithValue:error];
}
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
// This should overwrite the attachment pointer with an attachment stream.
[stream saveWithTransaction:transaction];
}];
return [AnyPromise promiseWithValue:@(1)];
}
- (void)logBackupMetadataCache:(YapDatabaseConnection *)dbConnection
{
OWSLogInfo(@"");
[dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[transaction enumerateKeysAndObjectsInCollection:[OWSBackupFragment collection]
usingBlock:^(NSString *key, OWSBackupFragment *fragment, BOOL *stop) {
OWSLogVerbose(@"fragment: %@, %@, %lu, %@, %@, %@, %@",
key,
fragment.recordName,
(unsigned long)fragment.encryptionKey.length,
fragment.relativeFilePath,
fragment.attachmentId,
fragment.downloadFilePath,
fragment.uncompressedDataLength);
}];
OWSLogVerbose(@"Number of fragments: %lu",
(unsigned long)[transaction numberOfKeysInCollection:[OWSBackupFragment collection]]);
}];
}
#pragma mark - Notifications
- (void)postDidChangeNotification
{
OWSAssertIsOnMainThread();
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:NSNotificationNameBackupStateDidChange
object:nil
userInfo:nil];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,740 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalUtilitiesKit
import CloudKit
import PromiseKit
// We don't worry about atomic writes. Each backup export
// will diff against last successful backup.
//
// Note that all of our CloudKit records are immutable.
// "Persistent" records are only uploaded once.
// "Ephemeral" records are always uploaded to a new record name.
@objc public class OWSBackupAPI: NSObject {
// If we change the record types, we need to ensure indices
// are configured properly in the CloudKit dashboard.
//
// TODO: Change the record types when we ship to production.
static let signalBackupRecordType = "signalBackup"
static let manifestRecordNameSuffix = "manifest"
static let payloadKey = "payload"
static let maxRetries = 5
private class func database() -> CKDatabase {
let myContainer = CKContainer.default()
let privateDatabase = myContainer.privateCloudDatabase
return privateDatabase
}
private class func invalidServiceResponseError() -> Error {
return OWSErrorWithCodeDescription(.backupFailure,
NSLocalizedString("BACKUP_EXPORT_ERROR_INVALID_CLOUDKIT_RESPONSE",
comment: "Error indicating that the app received an invalid response from CloudKit."))
}
// MARK: - Upload
@objc
public class func recordNameForTestFile(recipientId: String) -> String {
return "\(recordNamePrefix(forRecipientId: recipientId))test-\(NSUUID().uuidString)"
}
// "Ephemeral" files are specific to this backup export and will always need to
// be saved. For example, a complete image of the database is exported each time.
// We wouldn't want to overwrite previous images until the entire backup export is
// complete.
@objc
public class func recordNameForEphemeralFile(recipientId: String,
label: String) -> String {
return "\(recordNamePrefix(forRecipientId: recipientId))ephemeral-\(label)-\(NSUUID().uuidString)"
}
// "Persistent" files may be shared between backup export; they should only be saved
// once. For example, attachment files should only be uploaded once. Subsequent
// backups can reuse the same record.
@objc
public class func recordNameForPersistentFile(recipientId: String,
fileId: String) -> String {
return "\(recordNamePrefix(forRecipientId: recipientId))persistentFile-\(fileId)"
}
// "Persistent" files may be shared between backup export; they should only be saved
// once. For example, attachment files should only be uploaded once. Subsequent
// backups can reuse the same record.
@objc
public class func recordNameForManifest(recipientId: String) -> String {
return "\(recordNamePrefix(forRecipientId: recipientId))\(manifestRecordNameSuffix)"
}
private class func isManifest(recordName: String) -> Bool {
return recordName.hasSuffix(manifestRecordNameSuffix)
}
private class func recordNamePrefix(forRecipientId recipientId: String) -> String {
return "\(recipientId)-"
}
private class func recipientId(forRecordName recordName: String) -> String? {
let recipientIds = self.recipientIds(forRecordNames: [recordName])
guard let recipientId = recipientIds.first else {
return nil
}
return recipientId
}
private static var recordNamePrefixRegex = {
return try! NSRegularExpression(pattern: "^(\\+[0-9]+)\\-")
}()
private class func recipientIds(forRecordNames recordNames: [String]) -> [String] {
var recipientIds = [String]()
for recordName in recordNames {
let regex = recordNamePrefixRegex
guard let match: NSTextCheckingResult = regex.firstMatch(in: recordName, options: [], range: NSRange(location: 0, length: recordName.utf16.count)) else {
Logger.warn("no match: \(recordName)")
continue
}
guard match.numberOfRanges > 0 else {
// Match must include first group.
Logger.warn("invalid match: \(recordName)")
continue
}
let firstRange = match.range(at: 1)
guard firstRange.location == 0,
firstRange.length > 0 else {
// Match must be at start of string and non-empty.
Logger.warn("invalid match: \(recordName) \(firstRange)")
continue
}
let recipientId = (recordName as NSString).substring(with: firstRange) as String
recipientIds.append(recipientId)
}
return recipientIds
}
@objc
public class func record(forFileUrl fileUrl: URL,
recordName: String) -> CKRecord {
let recordType = signalBackupRecordType
let recordID = CKRecord.ID(recordName: recordName)
let record = CKRecord(recordType: recordType, recordID: recordID)
let asset = CKAsset(fileURL: fileUrl)
record[payloadKey] = asset
return record
}
@objc
public class func saveRecordsToCloudObjc(records: [CKRecord]) -> AnyPromise {
return AnyPromise(saveRecordsToCloud(records: records))
}
public class func saveRecordsToCloud(records: [CKRecord]) -> Promise<Void> {
// CloudKit's internal limit is 400, but I haven't found a constant for this.
let kMaxBatchSize = 100
return records.chunked(by: kMaxBatchSize).reduce(Promise.value(())) { (promise, batch) -> Promise<Void> in
return promise.then(on: .global()) {
saveRecordsToCloud(records: batch, remainingRetries: maxRetries)
}.done {
Logger.verbose("Saved batch: \(batch.count)")
}
}
}
private class func saveRecordsToCloud(records: [CKRecord],
remainingRetries: Int) -> Promise<Void> {
let recordNames = records.map { (record) in
return record.recordID.recordName
}
Logger.verbose("recordNames[\(recordNames.count)] \(recordNames[0..<10])...")
return Promise { resolver in
let saveOperation = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: nil)
saveOperation.modifyRecordsCompletionBlock = { (savedRecords: [CKRecord]?, _, error) in
let retry = {
// Only retry records which didn't already succeed.
var savedRecordNames = [String]()
if let savedRecords = savedRecords {
savedRecordNames = savedRecords.map { (record) in
return record.recordID.recordName
}
}
let retryRecords = records.filter({ (record) in
return !savedRecordNames.contains(record.recordID.recordName)
})
saveRecordsToCloud(records: retryRecords,
remainingRetries: remainingRetries - 1)
.done { _ in
resolver.fulfill(())
}.catch { (error) in
resolver.reject(error)
}.retainUntilComplete()
}
let outcome = outcomeForCloudKitError(error: error,
remainingRetries: remainingRetries,
label: "Save Records[\(recordNames.count)]")
switch outcome {
case .success:
resolver.fulfill(())
case .failureDoNotRetry(let outcomeError):
resolver.reject(outcomeError)
case .failureRetryAfterDelay(let retryDelay):
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
retry()
})
case .failureRetryWithoutDelay:
DispatchQueue.global().async {
retry()
}
case .unknownItem:
owsFailDebug("unexpected CloudKit response.")
resolver.reject(invalidServiceResponseError())
}
}
saveOperation.isAtomic = false
saveOperation.savePolicy = .allKeys
// TODO: use perRecordProgressBlock and perRecordCompletionBlock.
// open var perRecordProgressBlock: ((CKRecord, Double) -> Void)?
// open var perRecordCompletionBlock: ((CKRecord, Error?) -> Void)?
// These APIs are only available in iOS 9.3 and later.
if #available(iOS 9.3, *) {
saveOperation.isLongLived = true
saveOperation.qualityOfService = .background
}
database().add(saveOperation)
}
}
// MARK: - Delete
@objc
public class func deleteRecordsFromCloud(recordNames: [String],
success: @escaping () -> Void,
failure: @escaping (Error) -> Void) {
deleteRecordsFromCloud(recordNames: recordNames,
remainingRetries: maxRetries,
success: success,
failure: failure)
}
private class func deleteRecordsFromCloud(recordNames: [String],
remainingRetries: Int,
success: @escaping () -> Void,
failure: @escaping (Error) -> Void) {
let recordIDs = recordNames.map { CKRecord.ID(recordName: $0) }
let deleteOperation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: recordIDs)
deleteOperation.modifyRecordsCompletionBlock = { (records, recordIds, error) in
let outcome = outcomeForCloudKitError(error: error,
remainingRetries: remainingRetries,
label: "Delete Records")
switch outcome {
case .success:
success()
case .failureDoNotRetry(let outcomeError):
failure(outcomeError)
case .failureRetryAfterDelay(let retryDelay):
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
deleteRecordsFromCloud(recordNames: recordNames,
remainingRetries: remainingRetries - 1,
success: success,
failure: failure)
})
case .failureRetryWithoutDelay:
DispatchQueue.global().async {
deleteRecordsFromCloud(recordNames: recordNames,
remainingRetries: remainingRetries - 1,
success: success,
failure: failure)
}
case .unknownItem:
owsFailDebug("unexpected CloudKit response.")
failure(invalidServiceResponseError())
}
}
database().add(deleteOperation)
}
// MARK: - Exists?
private class func checkForFileInCloud(recordName: String,
remainingRetries: Int) -> Promise<CKRecord?> {
Logger.verbose("checkForFileInCloud \(recordName)")
let (promise, resolver) = Promise<CKRecord?>.pending()
let recordId = CKRecord.ID(recordName: recordName)
let fetchOperation = CKFetchRecordsOperation(recordIDs: [recordId ])
// Don't download the file; we're just using the fetch to check whether or
// not this record already exists.
fetchOperation.desiredKeys = []
fetchOperation.perRecordCompletionBlock = { (record, recordId, error) in
let outcome = outcomeForCloudKitError(error: error,
remainingRetries: remainingRetries,
label: "Check for Record")
switch outcome {
case .success:
guard let record = record else {
owsFailDebug("missing fetching record.")
resolver.reject(invalidServiceResponseError())
return
}
// Record found.
resolver.fulfill(record)
case .failureDoNotRetry(let outcomeError):
resolver.reject(outcomeError)
case .failureRetryAfterDelay(let retryDelay):
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
checkForFileInCloud(recordName: recordName,
remainingRetries: remainingRetries - 1)
.done { (record) in
resolver.fulfill(record)
}.catch { (error) in
resolver.reject(error)
}.retainUntilComplete()
})
case .failureRetryWithoutDelay:
DispatchQueue.global().async {
checkForFileInCloud(recordName: recordName,
remainingRetries: remainingRetries - 1)
.done { (record) in
resolver.fulfill(record)
}.catch { (error) in
resolver.reject(error)
}.retainUntilComplete()
}
case .unknownItem:
// Record not found.
resolver.fulfill(nil)
}
}
database().add(fetchOperation)
return promise
}
@objc
public class func checkForManifestInCloudObjc(recipientId: String) -> AnyPromise {
return AnyPromise(checkForManifestInCloud(recipientId: recipientId))
}
public class func checkForManifestInCloud(recipientId: String) -> Promise<Bool> {
let recordName = recordNameForManifest(recipientId: recipientId)
return checkForFileInCloud(recordName: recordName,
remainingRetries: maxRetries)
.map { (record) in
return record != nil
}
}
@objc
public class func allRecipientIdsWithManifestsInCloud(success: @escaping ([String]) -> Void,
failure: @escaping (Error) -> Void) {
let processResults = { (recordNames: [String]) in
DispatchQueue.global().async {
let manifestRecordNames = recordNames.filter({ (recordName) -> Bool in
self.isManifest(recordName: recordName)
})
let recipientIds = self.recipientIds(forRecordNames: manifestRecordNames)
success(recipientIds)
}
}
let query = CKQuery(recordType: signalBackupRecordType, predicate: NSPredicate(value: true))
// Fetch the first page of results for this query.
fetchAllRecordNamesStep(recipientId: nil,
query: query,
previousRecordNames: [String](),
cursor: nil,
remainingRetries: maxRetries,
success: processResults,
failure: failure)
}
@objc
public class func fetchAllRecordNames(recipientId: String,
success: @escaping ([String]) -> Void,
failure: @escaping (Error) -> Void) {
let query = CKQuery(recordType: signalBackupRecordType, predicate: NSPredicate(value: true))
// Fetch the first page of results for this query.
fetchAllRecordNamesStep(recipientId: recipientId,
query: query,
previousRecordNames: [String](),
cursor: nil,
remainingRetries: maxRetries,
success: success,
failure: failure)
}
private class func fetchAllRecordNamesStep(recipientId: String?,
query: CKQuery,
previousRecordNames: [String],
cursor: CKQueryOperation.Cursor?,
remainingRetries: Int,
success: @escaping ([String]) -> Void,
failure: @escaping (Error) -> Void) {
var allRecordNames = previousRecordNames
let queryOperation = CKQueryOperation(query: query)
// If this isn't the first page of results for this query, resume
// where we left off.
queryOperation.cursor = cursor
// Don't download the file; we're just using the query to get a list of record names.
queryOperation.desiredKeys = []
queryOperation.recordFetchedBlock = { (record) in
assert(record.recordID.recordName.count > 0)
let recordName = record.recordID.recordName
if let recipientId = recipientId {
let prefix = recordNamePrefix(forRecipientId: recipientId)
guard recordName.hasPrefix(prefix) else {
Logger.info("Ignoring record: \(recordName)")
return
}
}
allRecordNames.append(recordName)
}
queryOperation.queryCompletionBlock = { (cursor, error) in
let outcome = outcomeForCloudKitError(error: error,
remainingRetries: remainingRetries,
label: "Fetch All Records")
switch outcome {
case .success:
if let cursor = cursor {
Logger.verbose("fetching more record names \(allRecordNames.count).")
// There are more pages of results, continue fetching.
fetchAllRecordNamesStep(recipientId: recipientId,
query: query,
previousRecordNames: allRecordNames,
cursor: cursor,
remainingRetries: maxRetries,
success: success,
failure: failure)
return
}
Logger.info("fetched \(allRecordNames.count) record names.")
success(allRecordNames)
case .failureDoNotRetry(let outcomeError):
failure(outcomeError)
case .failureRetryAfterDelay(let retryDelay):
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
fetchAllRecordNamesStep(recipientId: recipientId,
query: query,
previousRecordNames: allRecordNames,
cursor: cursor,
remainingRetries: remainingRetries - 1,
success: success,
failure: failure)
})
case .failureRetryWithoutDelay:
DispatchQueue.global().async {
fetchAllRecordNamesStep(recipientId: recipientId,
query: query,
previousRecordNames: allRecordNames,
cursor: cursor,
remainingRetries: remainingRetries - 1,
success: success,
failure: failure)
}
case .unknownItem:
owsFailDebug("unexpected CloudKit response.")
failure(invalidServiceResponseError())
}
}
database().add(queryOperation)
}
// MARK: - Download
@objc
public class func downloadManifestFromCloudObjc(recipientId: String) -> AnyPromise {
return AnyPromise(downloadManifestFromCloud(recipientId: recipientId))
}
public class func downloadManifestFromCloud(recipientId: String) -> Promise<Data> {
let recordName = recordNameForManifest(recipientId: recipientId)
return downloadDataFromCloud(recordName: recordName)
}
@objc
public class func downloadDataFromCloudObjc(recordName: String) -> AnyPromise {
return AnyPromise(downloadDataFromCloud(recordName: recordName))
}
public class func downloadDataFromCloud(recordName: String) -> Promise<Data> {
return downloadFromCloud(recordName: recordName,
remainingRetries: maxRetries)
.map { (asset) -> Data in
guard let fileURL = asset.fileURL else {
throw invalidServiceResponseError()
}
return try Data(contentsOf: fileURL)
}
}
@objc
public class func downloadFileFromCloudObjc(recordName: String,
toFileUrl: URL) -> AnyPromise {
return AnyPromise(downloadFileFromCloud(recordName: recordName,
toFileUrl: toFileUrl))
}
public class func downloadFileFromCloud(recordName: String,
toFileUrl: URL) -> Promise<Void> {
return downloadFromCloud(recordName: recordName,
remainingRetries: maxRetries)
.done { asset in
guard let fileURL = asset.fileURL else {
throw invalidServiceResponseError()
}
try FileManager.default.copyItem(at: fileURL, to: toFileUrl)
}
}
// We return the CKAsset and not its fileUrl because
// CloudKit offers no guarantees around how long it'll
// keep around the underlying file. Presumably we can
// defer cleanup by maintaining a strong reference to
// the asset.
private class func downloadFromCloud(recordName: String,
remainingRetries: Int) -> Promise<CKAsset> {
Logger.verbose("downloadFromCloud \(recordName)")
let (promise, resolver) = Promise<CKAsset>.pending()
let recordId = CKRecord.ID(recordName: recordName)
let fetchOperation = CKFetchRecordsOperation(recordIDs: [recordId ])
// Download all keys for this record.
fetchOperation.perRecordCompletionBlock = { (record, recordId, error) in
let outcome = outcomeForCloudKitError(error: error,
remainingRetries: remainingRetries,
label: "Download Record")
switch outcome {
case .success:
guard let record = record else {
Logger.error("missing fetching record.")
resolver.reject(invalidServiceResponseError())
return
}
guard let asset = record[payloadKey] as? CKAsset else {
Logger.error("record missing payload.")
resolver.reject(invalidServiceResponseError())
return
}
resolver.fulfill(asset)
case .failureDoNotRetry(let outcomeError):
resolver.reject(outcomeError)
case .failureRetryAfterDelay(let retryDelay):
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
downloadFromCloud(recordName: recordName,
remainingRetries: remainingRetries - 1)
.done { (asset) in
resolver.fulfill(asset)
}.catch { (error) in
resolver.reject(error)
}.retainUntilComplete()
})
case .failureRetryWithoutDelay:
DispatchQueue.global().async {
downloadFromCloud(recordName: recordName,
remainingRetries: remainingRetries - 1)
.done { (asset) in
resolver.fulfill(asset)
}.catch { (error) in
resolver.reject(error)
}.retainUntilComplete()
}
case .unknownItem:
Logger.error("missing fetching record.")
resolver.reject(invalidServiceResponseError())
}
}
database().add(fetchOperation)
return promise
}
// MARK: - Access
@objc public enum BackupError: Int, Error {
case couldNotDetermineAccountStatus
case noAccount
case restrictedAccountStatus
}
@objc
public class func ensureCloudKitAccessObjc() -> AnyPromise {
return AnyPromise(ensureCloudKitAccess())
}
public class func ensureCloudKitAccess() -> Promise<Void> {
let (promise, resolver) = Promise<Void>.pending()
CKContainer.default().accountStatus { (accountStatus, error) in
if let error = error {
Logger.error("Unknown error: \(String(describing: error)).")
resolver.reject(error)
return
}
switch accountStatus {
case .couldNotDetermine:
Logger.error("could not determine CloudKit account status: \(String(describing: error)).")
resolver.reject(BackupError.couldNotDetermineAccountStatus)
case .noAccount:
Logger.error("no CloudKit account.")
resolver.reject(BackupError.noAccount)
case .restricted:
Logger.error("restricted CloudKit account.")
resolver.reject(BackupError.restrictedAccountStatus)
case .available:
Logger.verbose("CloudKit access okay.")
resolver.fulfill(())
default: resolver.fulfill(())
}
}
return promise
}
@objc
public class func errorMessage(forCloudKitAccessError error: Error) -> String {
if let backupError = error as? BackupError {
Logger.error("Backup error: \(String(describing: backupError)).")
switch backupError {
case .couldNotDetermineAccountStatus:
return NSLocalizedString("CLOUDKIT_STATUS_COULD_NOT_DETERMINE", comment: "Error indicating that the app could not determine that user's iCloud account status")
case .noAccount:
return NSLocalizedString("CLOUDKIT_STATUS_NO_ACCOUNT", comment: "Error indicating that user does not have an iCloud account.")
case .restrictedAccountStatus:
return NSLocalizedString("CLOUDKIT_STATUS_RESTRICTED", comment: "Error indicating that the app was prevented from accessing the user's iCloud account.")
}
} else {
Logger.error("Unknown error: \(String(describing: error)).")
return NSLocalizedString("CLOUDKIT_STATUS_COULD_NOT_DETERMINE", comment: "Error indicating that the app could not determine that user's iCloud account status")
}
}
// MARK: - Retry
private enum APIOutcome {
case success
case failureDoNotRetry(error:Error)
case failureRetryAfterDelay(retryDelay: TimeInterval)
case failureRetryWithoutDelay
// This only applies to fetches.
case unknownItem
}
private class func outcomeForCloudKitError(error: Error?,
remainingRetries: Int,
label: String) -> APIOutcome {
if let error = error as? CKError {
if error.code == CKError.unknownItem {
// This is not always an error for our purposes.
Logger.verbose("\(label) unknown item.")
return .unknownItem
}
Logger.error("\(label) failed: \(error)")
if remainingRetries < 1 {
Logger.verbose("\(label) no more retries.")
return .failureDoNotRetry(error:error)
}
if #available(iOS 11, *) {
if error.code == CKError.serverResponseLost {
Logger.verbose("\(label) retry without delay.")
return .failureRetryWithoutDelay
}
}
switch error {
case CKError.requestRateLimited, CKError.serviceUnavailable, CKError.zoneBusy:
let retryDelay = error.retryAfterSeconds ?? 3.0
Logger.verbose("\(label) retry with delay: \(retryDelay).")
return .failureRetryAfterDelay(retryDelay:retryDelay)
case CKError.networkFailure:
Logger.verbose("\(label) retry without delay.")
return .failureRetryWithoutDelay
default:
Logger.verbose("\(label) unknown CKError.")
return .failureDoNotRetry(error:error)
}
} else if let error = error {
Logger.error("\(label) failed: \(error)")
if remainingRetries < 1 {
Logger.verbose("\(label) no more retries.")
return .failureDoNotRetry(error:error)
}
Logger.verbose("\(label) unknown error.")
return .failureDoNotRetry(error:error)
} else {
Logger.info("\(label) succeeded.")
return .success
}
}
// MARK: -
@objc
public class func setup() {
cancelAllLongLivedOperations()
}
private class func cancelAllLongLivedOperations() {
// These APIs are only available in iOS 9.3 and later.
guard #available(iOS 9.3, *) else {
return
}
let container = CKContainer.default()
container.fetchAllLongLivedOperationIDs { (operationIds, error) in
if let error = error {
Logger.error("Could not get all long lived operations: \(error)")
return
}
guard let operationIds = operationIds else {
Logger.error("No operation ids.")
return
}
for operationId in operationIds {
container.fetchLongLivedOperation(withID: operationId, completionHandler: { (operation, error) in
if let error = error {
Logger.error("Could not get long lived operation [\(operationId)]: \(error)")
return
}
guard let operation = operation else {
Logger.error("No operation.")
return
}
operation.cancel()
})
}
}
}
}

View File

@ -1,15 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSBackupJob.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSBackupExportJob : OWSBackupJob
- (void)start;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@ -1,61 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface OWSBackupEncryptedItem : NSObject
@property (nonatomic) NSString *filePath;
@property (nonatomic) NSData *encryptionKey;
@end
#pragma mark -
@interface OWSBackupIO : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithJobTempDirPath:(NSString *)jobTempDirPath;
- (NSString *)generateTempFilePath;
- (nullable NSString *)createTempFile;
#pragma mark - Encrypt
- (nullable OWSBackupEncryptedItem *)encryptFileAsTempFile:(NSString *)srcFilePath;
- (nullable OWSBackupEncryptedItem *)encryptFileAsTempFile:(NSString *)srcFilePath
encryptionKey:(NSData *)encryptionKey;
- (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)srcData;
- (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)srcData encryptionKey:(NSData *)encryptionKey;
#pragma mark - Decrypt
- (BOOL)decryptFileAsFile:(NSString *)srcFilePath
dstFilePath:(NSString *)dstFilePath
encryptionKey:(NSData *)encryptionKey;
- (nullable NSData *)decryptFileAsData:(NSString *)srcFilePath encryptionKey:(NSData *)encryptionKey;
- (nullable NSData *)decryptDataAsData:(NSData *)srcData encryptionKey:(NSData *)encryptionKey;
#pragma mark - Compression
- (nullable NSData *)compressData:(NSData *)srcData;
// I'm using the (new in iOS 9) compressionlib. One of its weaknesses is that it
// requires you to pre-allocate output buffers during compression and decompression.
// During decompression this is particularly tricky since there's no way to safely
// predict how large the output will be based on the input. So, we store the
// uncompressed size for compressed backup items.
- (nullable NSData *)decompressData:(NSData *)srcData uncompressedDataLength:(NSUInteger)uncompressedDataLength;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,273 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSBackupIO.h"
#import <SignalCoreKit/Randomness.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>
@import Compression;
NS_ASSUME_NONNULL_BEGIN
// TODO:
static const NSUInteger kOWSBackupKeyLength = 32;
// LZMA algorithm significantly outperforms the other compressionlib options
// for our database snapshots and is a widely adopted standard.
static const compression_algorithm SignalCompressionAlgorithm = COMPRESSION_LZMA;
@implementation OWSBackupEncryptedItem
@end
#pragma mark -
@interface OWSBackupIO ()
@property (nonatomic) NSString *jobTempDirPath;
@end
#pragma mark -
@implementation OWSBackupIO
- (instancetype)initWithJobTempDirPath:(NSString *)jobTempDirPath
{
if (!(self = [super init])) {
return self;
}
self.jobTempDirPath = jobTempDirPath;
return self;
}
- (NSString *)generateTempFilePath
{
return [self.jobTempDirPath stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
}
- (nullable NSString *)createTempFile
{
NSString *filePath = [self generateTempFilePath];
if (![OWSFileSystem ensureFileExists:filePath]) {
OWSFailDebug(@"could not create temp file.");
return nil;
}
return filePath;
}
#pragma mark - Encrypt
- (nullable OWSBackupEncryptedItem *)encryptFileAsTempFile:(NSString *)srcFilePath
{
OWSAssertDebug(srcFilePath.length > 0);
NSData *encryptionKey = [Randomness generateRandomBytes:(int)kOWSBackupKeyLength];
return [self encryptFileAsTempFile:srcFilePath encryptionKey:encryptionKey];
}
- (nullable OWSBackupEncryptedItem *)encryptFileAsTempFile:(NSString *)srcFilePath encryptionKey:(NSData *)encryptionKey
{
OWSAssertDebug(srcFilePath.length > 0);
OWSAssertDebug(encryptionKey.length > 0);
@autoreleasepool {
if (![[NSFileManager defaultManager] fileExistsAtPath:srcFilePath]) {
OWSFailDebug(@"Missing source file.");
return nil;
}
// TODO: Encrypt the file without loading it into memory.
NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];
if (srcData.length < 1) {
OWSFailDebug(@"could not load file into memory for encryption.");
return nil;
}
return [self encryptDataAsTempFile:srcData encryptionKey:encryptionKey];
}
}
- (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)srcData
{
OWSAssertDebug(srcData);
NSData *encryptionKey = [Randomness generateRandomBytes:(int)kOWSBackupKeyLength];
return [self encryptDataAsTempFile:srcData encryptionKey:encryptionKey];
}
- (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)unencryptedData
encryptionKey:(NSData *)encryptionKey
{
OWSAssertDebug(unencryptedData);
OWSAssertDebug(encryptionKey.length > 0);
@autoreleasepool {
// TODO: Encrypt the data using key;
NSData *encryptedData = unencryptedData;
NSString *_Nullable dstFilePath = [self createTempFile];
if (!dstFilePath) {
return nil;
}
NSError *error;
BOOL success = [encryptedData writeToFile:dstFilePath options:NSDataWritingAtomic error:&error];
if (!success || error) {
OWSFailDebug(@"error writing encrypted data: %@", error);
return nil;
}
[OWSFileSystem protectFileOrFolderAtPath:dstFilePath];
OWSBackupEncryptedItem *item = [OWSBackupEncryptedItem new];
item.filePath = dstFilePath;
item.encryptionKey = encryptionKey;
return item;
}
}
#pragma mark - Decrypt
- (BOOL)decryptFileAsFile:(NSString *)srcFilePath
dstFilePath:(NSString *)dstFilePath
encryptionKey:(NSData *)encryptionKey
{
OWSAssertDebug(srcFilePath.length > 0);
OWSAssertDebug(encryptionKey.length > 0);
@autoreleasepool {
// TODO: Decrypt the file without loading it into memory.
NSData *data = [self decryptFileAsData:srcFilePath encryptionKey:encryptionKey];
if (data.length < 1) {
return NO;
}
NSError *error;
BOOL success = [data writeToFile:dstFilePath options:NSDataWritingAtomic error:&error];
if (!success || error) {
OWSFailDebug(@"error writing decrypted data: %@", error);
return NO;
}
[OWSFileSystem protectFileOrFolderAtPath:dstFilePath];
return YES;
}
}
- (nullable NSData *)decryptFileAsData:(NSString *)srcFilePath encryptionKey:(NSData *)encryptionKey
{
OWSAssertDebug(srcFilePath.length > 0);
OWSAssertDebug(encryptionKey.length > 0);
@autoreleasepool {
if (![NSFileManager.defaultManager fileExistsAtPath:srcFilePath]) {
OWSLogError(@"missing downloaded file.");
return nil;
}
NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];
if (srcData.length < 1) {
OWSFailDebug(@"could not load file into memory for decryption.");
return nil;
}
NSData *_Nullable dstData = [self decryptDataAsData:srcData encryptionKey:encryptionKey];
return dstData;
}
}
- (nullable NSData *)decryptDataAsData:(NSData *)encryptedData encryptionKey:(NSData *)encryptionKey
{
OWSAssertDebug(encryptedData);
OWSAssertDebug(encryptionKey.length > 0);
@autoreleasepool {
// TODO: Decrypt the data using key;
NSData *unencryptedData = encryptedData;
return unencryptedData;
}
}
#pragma mark - Compression
- (nullable NSData *)compressData:(NSData *)srcData
{
OWSAssertDebug(srcData);
@autoreleasepool {
if (!srcData) {
OWSFailDebug(@"missing unencrypted data.");
return nil;
}
size_t srcLength = [srcData length];
// This assumes that dst will always be smaller than src.
//
// We slightly pad the buffer size to account for the worst case.
size_t dstBufferLength = srcLength + 64 * 1024;
NSMutableData *dstBufferData = [NSMutableData dataWithLength:dstBufferLength];
if (!dstBufferData) {
OWSFailDebug(@"Failed to allocate buffer.");
return nil;
}
size_t dstLength = compression_encode_buffer(
dstBufferData.mutableBytes, dstBufferLength, srcData.bytes, srcLength, NULL, SignalCompressionAlgorithm);
NSData *compressedData = [dstBufferData subdataWithRange:NSMakeRange(0, dstLength)];
OWSLogVerbose(@"compressed %zd -> %zd = %0.2f",
srcLength,
dstLength,
(srcLength > 0 ? (dstLength / (CGFloat)srcLength) : 0));
return compressedData;
}
}
- (nullable NSData *)decompressData:(NSData *)srcData uncompressedDataLength:(NSUInteger)uncompressedDataLength
{
OWSAssertDebug(srcData);
@autoreleasepool {
if (!srcData) {
OWSFailDebug(@"missing unencrypted data.");
return nil;
}
size_t srcLength = [srcData length];
// We pad the buffer to be defensive.
size_t dstBufferLength = uncompressedDataLength + 1024;
NSMutableData *dstBufferData = [NSMutableData dataWithLength:dstBufferLength];
if (!dstBufferData) {
OWSFailDebug(@"Failed to allocate buffer.");
return nil;
}
size_t dstLength = compression_decode_buffer(
dstBufferData.mutableBytes, dstBufferLength, srcData.bytes, srcLength, NULL, SignalCompressionAlgorithm);
NSData *decompressedData = [dstBufferData subdataWithRange:NSMakeRange(0, dstLength)];
OWSAssertDebug(decompressedData.length == uncompressedDataLength);
OWSLogVerbose(@"decompressed %zd -> %zd = %0.2f",
srcLength,
dstLength,
(dstLength > 0 ? (srcLength / (CGFloat)dstLength) : 0));
return decompressedData;
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,15 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSBackupJob.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSBackupImportJob : OWSBackupJob
- (void)start;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,635 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSBackupImportJob.h"
#import "OWSBackupIO.h"
#import "OWSDatabaseMigration.h"
#import "OWSDatabaseMigrationRunner.h"
#import "Session-Swift.h"
#import <PromiseKit/AnyPromise.h>
#import <SignalCoreKit/NSData+OWS.h>
#import <SessionMessagingKit/OWSBackgroundTask.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>
#import <SessionMessagingKit/TSAttachment.h>
#import <SessionMessagingKit/TSMessage.h>
#import <SessionMessagingKit/TSThread.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKeySpec";
#pragma mark -
@interface OWSBackupImportJob ()
@property (nonatomic, nullable) OWSBackgroundTask *backgroundTask;
@property (nonatomic) OWSBackupIO *backupIO;
@property (nonatomic) OWSBackupManifestContents *manifest;
@property (nonatomic, nullable) YapDatabaseConnection *dbConnection;
@end
#pragma mark -
@implementation OWSBackupImportJob
#pragma mark - Dependencies
- (OWSPrimaryStorage *)primaryStorage
{
OWSAssertDebug(SSKEnvironment.shared.primaryStorage);
return SSKEnvironment.shared.primaryStorage;
}
- (OWSProfileManager *)profileManager
{
return [OWSProfileManager sharedManager];
}
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
- (OWSBackup *)backup
{
OWSAssertDebug(AppEnvironment.shared.backup);
return AppEnvironment.shared.backup;
}
- (OWSBackupLazyRestore *)backupLazyRestore
{
return AppEnvironment.shared.backupLazyRestore;
}
#pragma mark -
- (NSArray<OWSBackupFragment *> *)databaseItems
{
OWSAssertDebug(self.manifest);
return self.manifest.databaseItems;
}
- (NSArray<OWSBackupFragment *> *)attachmentsItems
{
OWSAssertDebug(self.manifest);
return self.manifest.attachmentsItems;
}
- (void)start
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
self.backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
self.dbConnection = self.primaryStorage.newDatabaseConnection;
[self updateProgressWithDescription:nil progress:nil];
[[self.backup ensureCloudKitAccess]
.thenInBackground(^{
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_IMPORT_PHASE_CONFIGURATION",
@"Indicates that the backup import is being configured.")
progress:nil];
return [self configureImport];
})
.thenInBackground(^{
if (self.isComplete) {
return
[AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")];
}
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_IMPORT_PHASE_IMPORT",
@"Indicates that the backup import data is being imported.")
progress:nil];
return [self downloadAndProcessManifestWithBackupIO:self.backupIO];
})
.thenInBackground(^(OWSBackupManifestContents *manifest) {
OWSCAssertDebug(manifest.databaseItems.count > 0);
OWSCAssertDebug(manifest.attachmentsItems);
self.manifest = manifest;
return [self downloadAndProcessImport];
})
.catch(^(NSError *error) {
[self failWithErrorDescription:
NSLocalizedString(@"BACKUP_IMPORT_ERROR_COULD_NOT_IMPORT",
@"Error indicating the backup import could not import the user's data.")];
}) retainUntilComplete];
}
- (AnyPromise *)downloadAndProcessImport
{
OWSAssertDebug(self.databaseItems);
OWSAssertDebug(self.attachmentsItems);
// These items should be downloaded immediately.
NSMutableArray<OWSBackupFragment *> *allItems = [NSMutableArray new];
[allItems addObjectsFromArray:self.databaseItems];
// Make a copy of the blockingItems before we add
// the optional items.
NSArray<OWSBackupFragment *> *blockingItems = [allItems copy];
// Local profile avatars are optional in the sense that if their
// download fails, we want to proceed with the import.
if (self.manifest.localProfileAvatarItem) {
[allItems addObject:self.manifest.localProfileAvatarItem];
}
// Attachment items can be downloaded later;
// they will can be lazy-restored.
[allItems addObjectsFromArray:self.attachmentsItems];
// Record metadata for all items, so that we can re-use them in incremental backups after the restore.
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (OWSBackupFragment *item in allItems) {
[item saveWithTransaction:transaction];
}
}];
return [self downloadFilesFromCloud:blockingItems]
.thenInBackground(^{
return [self restoreDatabase];
})
.thenInBackground(^{
return [self ensureMigrations];
})
.thenInBackground(^{
return [self restoreLocalProfile];
})
.thenInBackground(^{
return [self restoreAttachmentFiles];
})
.then(^{
// Kick off lazy restore on main thread.
[self.backupLazyRestore clearCompleteAndRunIfNecessary];
// Make sure backup is enabled once we complete
// a backup restore.
[OWSBackup.sharedManager setIsBackupEnabled:YES];
})
.thenInBackground(^{
return [self.tsAccountManager updateAccountAttributes];
})
.thenInBackground(^{
[self succeed];
});
}
- (AnyPromise *)configureImport
{
OWSLogVerbose(@"");
if (![self ensureJobTempDir]) {
OWSFailDebug(@"Could not create jobTempDirPath.");
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Could not create jobTempDirPath.")];
}
self.backupIO = [[OWSBackupIO alloc] initWithJobTempDirPath:self.jobTempDirPath];
return [AnyPromise promiseWithValue:@(1)];
}
- (AnyPromise *)downloadFilesFromCloud:(NSArray<OWSBackupFragment *> *)items
{
OWSAssertDebug(items.count > 0);
OWSLogVerbose(@"");
NSUInteger recordCount = items.count;
if (self.isComplete) {
// Job was aborted.
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")];
}
if (items.count < 1) {
// All downloads are complete; exit.
return [AnyPromise promiseWithValue:@(1)];
}
AnyPromise *promise = [AnyPromise promiseWithValue:@(1)];
for (OWSBackupFragment *item in items) {
promise = promise
.thenInBackground(^{
CGFloat progress
= (recordCount > 0 ? ((recordCount - items.count) / (CGFloat)recordCount) : 0.f);
[self updateProgressWithDescription:
NSLocalizedString(@"BACKUP_IMPORT_PHASE_DOWNLOAD",
@"Indicates that the backup import data is being downloaded.")
progress:@(progress)];
})
.thenInBackground(^{
return [self downloadFileFromCloud:item];
});
}
return promise;
}
- (AnyPromise *)downloadFileFromCloud:(OWSBackupFragment *)item
{
OWSAssertDebug(item);
OWSLogVerbose(@"");
if (self.isComplete) {
// Job was aborted.
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")];
}
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
// TODO: Use a predictable file path so that multiple "import backup" attempts
// will leverage successful file downloads from previous attempts.
//
// TODO: This will also require imports using a predictable jobTempDirPath.
NSString *tempFilePath = [self.jobTempDirPath stringByAppendingPathComponent:item.recordName];
// Skip redundant file download.
if ([NSFileManager.defaultManager fileExistsAtPath:tempFilePath]) {
[OWSFileSystem protectFileOrFolderAtPath:tempFilePath];
item.downloadFilePath = tempFilePath;
return resolve(@(1));
}
[OWSBackupAPI downloadFileFromCloudObjcWithRecordName:item.recordName
toFileUrl:[NSURL fileURLWithPath:tempFilePath]]
.thenInBackground(^{
[OWSFileSystem protectFileOrFolderAtPath:tempFilePath];
item.downloadFilePath = tempFilePath;
resolve(@(1));
})
.catchInBackground(^(NSError *error) {
resolve(error);
});
}];
}
- (AnyPromise *)restoreLocalProfile
{
OWSLogVerbose(@"");
if (self.isComplete) {
// Job was aborted.
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")];
}
AnyPromise *promise = [AnyPromise promiseWithValue:@(1)];
if (self.manifest.localProfileAvatarItem) {
promise = promise.thenInBackground(^{
return
[self downloadFileFromCloud:self.manifest.localProfileAvatarItem].catchInBackground(^(NSError *error) {
OWSLogInfo(@"Ignoring error; profiles are optional: %@", error);
});
});
}
promise = promise.thenInBackground(^{
return [self applyLocalProfile];
});
return promise;
}
- (AnyPromise *)applyLocalProfile
{
OWSLogVerbose(@"");
if (self.isComplete) {
// Job was aborted.
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")];
}
NSString *_Nullable localProfileName = self.manifest.localProfileName;
UIImage *_Nullable localProfileAvatar = [self tryToLoadLocalProfileAvatar];
OWSLogVerbose(@"local profile name: %@, avatar: %d", localProfileName, localProfileAvatar != nil);
if (localProfileName.length < 1 && !localProfileAvatar) {
return [AnyPromise promiseWithValue:@(1)];
}
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[self.profileManager updateLocalProfileName:localProfileName
avatarImage:localProfileAvatar
success:^{
resolve(@(1));
}
failure:^(NSError *error) {
// Ignore errors related to local profile.
resolve(@(1));
}
requiresSync:YES];
}];
}
- (nullable UIImage *)tryToLoadLocalProfileAvatar
{
if (!self.manifest.localProfileAvatarItem) {
return nil;
}
if (!self.manifest.localProfileAvatarItem.downloadFilePath) {
// Download of the avatar failed.
// We can safely ignore errors related to local profile.
OWSLogError(@"local profile avatar was not downloaded.");
return nil;
}
OWSBackupFragment *item = self.manifest.localProfileAvatarItem;
if (item.recordName.length < 1) {
OWSFailDebug(@"item missing record name.");
return nil;
}
@autoreleasepool {
NSData *_Nullable data =
[self.backupIO decryptFileAsData:item.downloadFilePath encryptionKey:item.encryptionKey];
if (!data) {
OWSLogError(@"could not decrypt local profile avatar.");
// Ignore errors related to local profile.
return nil;
}
// TODO: Verify that we're not compressing the profile avatar data.
UIImage *_Nullable image = [UIImage imageWithData:data];
if (!image) {
OWSLogError(@"could not decrypt local profile avatar.");
// Ignore errors related to local profile.
return nil;
}
return image;
}
}
- (AnyPromise *)restoreAttachmentFiles
{
OWSLogVerbose(@": %zd", self.attachmentsItems.count);
if (self.isComplete) {
// Job was aborted.
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")];
}
__block NSUInteger count = 0;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (OWSBackupFragment *item in self.attachmentsItems) {
if (self.isComplete) {
return;
}
if (item.recordName.length < 1) {
OWSLogError(@"attachment was not downloaded.");
// Attachment-related errors are recoverable and can be ignored.
continue;
}
if (item.attachmentId.length < 1) {
OWSLogError(@"attachment missing attachment id.");
// Attachment-related errors are recoverable and can be ignored.
continue;
}
if (item.relativeFilePath.length < 1) {
OWSLogError(@"attachment missing relative file path.");
// Attachment-related errors are recoverable and can be ignored.
continue;
}
TSAttachmentPointer *_Nullable attachment =
[TSAttachmentPointer fetchObjectWithUniqueID:item.attachmentId transaction:transaction];
if (!attachment) {
OWSLogError(@"attachment to restore could not be found.");
// Attachment-related errors are recoverable and can be ignored.
continue;
}
if (![attachment isKindOfClass:[TSAttachmentPointer class]]) {
OWSFailDebug(@"attachment has unexpected type: %@.", attachment.class);
// Attachment-related errors are recoverable and can be ignored.
continue;
}
[attachment markForLazyRestoreWithFragment:item transaction:transaction];
count++;
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_IMPORT_PHASE_RESTORING_FILES",
@"Indicates that the backup import data is being restored.")
progress:@(count / (CGFloat)self.attachmentsItems.count)];
}
}];
OWSLogError(@"enqueued lazy restore of %zd files.", count);
return [AnyPromise promiseWithValue:@(1)];
}
- (AnyPromise *)restoreDatabase
{
OWSLogVerbose(@"");
if (self.isComplete) {
// Job was aborted.
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")];
}
// Order matters here.
NSArray<NSString *> *collectionsToRestore = @[
[TSThread collection],
[TSAttachment collection],
// Interactions refer to threads and attachments,
// so copy them afterward.
[TSInteraction collection],
[OWSDatabaseMigration collection],
];
NSMutableDictionary<NSString *, NSNumber *> *restoredEntityCounts = [NSMutableDictionary new];
__block unsigned long long copiedEntities = 0;
__block BOOL aborted = NO;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *collection in collectionsToRestore) {
if ([collection isEqualToString:[OWSDatabaseMigration collection]]) {
// It's okay if there are existing migrations; we'll clear those
// before restoring.
continue;
}
if ([transaction numberOfKeysInCollection:collection] > 0) {
OWSLogError(@"unexpected contents in database (%@).", collection);
}
}
// Clear existing database contents.
//
// This should be safe since we only ever import into an empty database.
//
// Note that if the app receives a message after registering and before restoring
// backup, it will be lost.
//
// Note that this will clear all migrations.
for (NSString *collection in collectionsToRestore) {
[transaction removeAllObjectsInCollection:collection];
}
NSUInteger count = 0;
for (OWSBackupFragment *item in self.databaseItems) {
if (self.isComplete) {
return;
}
if (item.recordName.length < 1) {
OWSLogError(@"database snapshot was not downloaded.");
// Attachment-related errors are recoverable and can be ignored.
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
if (!item.uncompressedDataLength || item.uncompressedDataLength.unsignedIntValue < 1) {
OWSLogError(@"database snapshot missing size.");
// Attachment-related errors are recoverable and can be ignored.
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
count++;
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_IMPORT_PHASE_RESTORING_DATABASE",
@"Indicates that the backup database is being restored.")
progress:@(count / (CGFloat)self.databaseItems.count)];
@autoreleasepool {
NSData *_Nullable compressedData =
[self.backupIO decryptFileAsData:item.downloadFilePath encryptionKey:item.encryptionKey];
if (!compressedData) {
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
NSData *_Nullable uncompressedData =
[self.backupIO decompressData:compressedData
uncompressedDataLength:item.uncompressedDataLength.unsignedIntValue];
if (!uncompressedData) {
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
NSError *error;
SignalIOSProtoBackupSnapshot *_Nullable entities =
[SignalIOSProtoBackupSnapshot parseData:uncompressedData error:&error];
if (!entities || error) {
OWSLogError(@"could not parse proto: %@.", error);
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
if (!entities || entities.entity.count < 1) {
OWSLogError(@"missing entities.");
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
for (SignalIOSProtoBackupSnapshotBackupEntity *entity in entities.entity) {
NSData *_Nullable entityData = entity.entityData;
if (entityData.length < 1) {
OWSLogError(@"missing entity data.");
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
NSString *_Nullable collection = entity.collection;
if (collection.length < 1) {
OWSLogError(@"missing collection.");
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
NSString *_Nullable key = entity.key;
if (key.length < 1) {
OWSLogError(@"missing key.");
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
__block NSObject *object = nil;
@try {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:entityData];
object = [unarchiver decodeObjectForKey:@"root"];
if (![object isKindOfClass:[object class]]) {
OWSLogError(@"invalid decoded entity: %@.", [object class]);
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
} @catch (NSException *exception) {
OWSLogError(@"could not decode entity.");
// Database-related errors are unrecoverable.
aborted = YES;
return;
}
[transaction setObject:object forKey:key inCollection:collection];
copiedEntities++;
NSUInteger restoredEntityCount = restoredEntityCounts[collection].unsignedIntValue;
restoredEntityCounts[collection] = @(restoredEntityCount + 1);
}
}
}
}];
if (aborted) {
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import failed.")];
}
if (self.isComplete) {
// Job was aborted.
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")];
}
for (NSString *collection in restoredEntityCounts) {
OWSLogInfo(@"copied %@: %@", collection, restoredEntityCounts[collection]);
}
OWSLogInfo(@"copiedEntities: %llu", copiedEntities);
[self.primaryStorage logFileSizes];
return [AnyPromise promiseWithValue:@(1)];
}
- (AnyPromise *)ensureMigrations
{
OWSLogVerbose(@"");
if (self.isComplete) {
// Job was aborted.
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")];
}
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_IMPORT_PHASE_FINALIZING",
@"Indicates that the backup import data is being finalized.")
progress:nil];
// It's okay that we do this in a separate transaction from the
// restoration of backup contents. If some of migrations don't
// complete, they'll be run the next time the app launches.
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
dispatch_async(dispatch_get_main_queue(), ^{
[[[OWSDatabaseMigrationRunner alloc] init] runAllOutstandingWithCompletion:^(BOOL successful, BOOL needsConfigSync){
resolve(@(1));
}];
});
}];
return promise;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,92 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "TSYapDatabaseObject.h"
#import <SessionMessagingKit/OWSBackupFragment.h>
NS_ASSUME_NONNULL_BEGIN
extern NSString *const kOWSBackup_ManifestKey_DatabaseFiles;
extern NSString *const kOWSBackup_ManifestKey_AttachmentFiles;
extern NSString *const kOWSBackup_ManifestKey_RecordName;
extern NSString *const kOWSBackup_ManifestKey_EncryptionKey;
extern NSString *const kOWSBackup_ManifestKey_RelativeFilePath;
extern NSString *const kOWSBackup_ManifestKey_AttachmentId;
extern NSString *const kOWSBackup_ManifestKey_DataSize;
extern NSString *const kOWSBackup_ManifestKey_LocalProfileAvatar;
extern NSString *const kOWSBackup_ManifestKey_LocalProfileName;
@class AnyPromise;
@class OWSBackupIO;
@class OWSBackupJob;
@class OWSBackupManifestContents;
typedef void (^OWSBackupJobBoolCompletion)(BOOL success);
typedef void (^OWSBackupJobCompletion)(NSError *_Nullable error);
typedef void (^OWSBackupJobManifestSuccess)(OWSBackupManifestContents *manifest);
typedef void (^OWSBackupJobManifestFailure)(NSError *error);
@interface OWSBackupManifestContents : NSObject
@property (nonatomic) NSArray<OWSBackupFragment *> *databaseItems;
@property (nonatomic) NSArray<OWSBackupFragment *> *attachmentsItems;
@property (nonatomic, nullable) OWSBackupFragment *localProfileAvatarItem;
@property (nonatomic, nullable) NSString *localProfileName;
@end
#pragma mark -
@protocol OWSBackupJobDelegate <NSObject>
- (nullable NSData *)backupEncryptionKey;
// Either backupJobDidSucceed:... or backupJobDidFail:... will
// be called exactly once on the main thread UNLESS:
//
// * The job was never started.
// * The job was cancelled.
- (void)backupJobDidSucceed:(OWSBackupJob *)backupJob;
- (void)backupJobDidFail:(OWSBackupJob *)backupJob error:(NSError *)error;
- (void)backupJobDidUpdate:(OWSBackupJob *)backupJob
description:(nullable NSString *)description
progress:(nullable NSNumber *)progress;
@end
#pragma mark -
@interface OWSBackupJob : NSObject
@property (nonatomic, weak, readonly) id<OWSBackupJobDelegate> delegate;
@property (nonatomic, readonly) NSString *recipientId;
// Indicates that the backup succeeded, failed or was cancelled.
@property (atomic, readonly) BOOL isComplete;
@property (nonatomic, readonly) NSString *jobTempDirPath;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithDelegate:(id<OWSBackupJobDelegate>)delegate recipientId:(NSString *)recipientId;
#pragma mark - Private
- (BOOL)ensureJobTempDir;
- (void)cancel;
- (void)succeed;
- (void)failWithErrorDescription:(NSString *)description;
- (void)failWithError:(NSError *)error;
- (void)updateProgressWithDescription:(nullable NSString *)description progress:(nullable NSNumber *)progress;
#pragma mark - Manifest
- (AnyPromise *)downloadAndProcessManifestWithBackupIO:(OWSBackupIO *)backupIO __attribute__((warn_unused_result));
@end
NS_ASSUME_NONNULL_END

View File

@ -1,316 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSBackupJob.h"
#import "OWSBackupIO.h"
#import "Session-Swift.h"
#import <PromiseKit/AnyPromise.h>
#import <SignalCoreKit/Randomness.h>
#import <YapDatabase/YapDatabaseCryptoUtils.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const kOWSBackup_ManifestKey_DatabaseFiles = @"database_files";
NSString *const kOWSBackup_ManifestKey_AttachmentFiles = @"attachment_files";
NSString *const kOWSBackup_ManifestKey_RecordName = @"record_name";
NSString *const kOWSBackup_ManifestKey_EncryptionKey = @"encryption_key";
NSString *const kOWSBackup_ManifestKey_RelativeFilePath = @"relative_file_path";
NSString *const kOWSBackup_ManifestKey_AttachmentId = @"attachment_id";
NSString *const kOWSBackup_ManifestKey_DataSize = @"data_size";
NSString *const kOWSBackup_ManifestKey_LocalProfileAvatar = @"local_profile_avatar";
NSString *const kOWSBackup_ManifestKey_LocalProfileName = @"local_profile_name";
NSString *const kOWSBackup_KeychainService = @"kOWSBackup_KeychainService";
@implementation OWSBackupManifestContents
@end
#pragma mark -
@interface OWSBackupJob ()
@property (nonatomic, weak) id<OWSBackupJobDelegate> delegate;
@property (nonatomic) NSString *recipientId;
@property (atomic) BOOL isComplete;
@property (atomic) BOOL hasSucceeded;
@property (nonatomic) NSString *jobTempDirPath;
@end
#pragma mark -
@implementation OWSBackupJob
- (instancetype)initWithDelegate:(id<OWSBackupJobDelegate>)delegate recipientId:(NSString *)recipientId
{
self = [super init];
if (!self) {
return self;
}
OWSAssertDebug(recipientId.length > 0);
OWSAssertDebug([OWSStorage isStorageReady]);
self.delegate = delegate;
self.recipientId = recipientId;
return self;
}
- (void)dealloc
{
// Surface memory leaks by logging the deallocation.
OWSLogVerbose(@"Dealloc: %@", self.class);
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (self.jobTempDirPath) {
[OWSFileSystem deleteFileIfExists:self.jobTempDirPath];
}
}
- (BOOL)ensureJobTempDir
{
OWSLogVerbose(@"");
// TODO: Exports should use a new directory each time, but imports
// might want to use a predictable directory so that repeated
// import attempts can reuse downloads from previous attempts.
NSString *temporaryDirectory = OWSTemporaryDirectory();
self.jobTempDirPath = [temporaryDirectory stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
if (![OWSFileSystem ensureDirectoryExists:self.jobTempDirPath]) {
OWSFailDebug(@"Could not create jobTempDirPath.");
return NO;
}
return YES;
}
#pragma mark -
- (void)cancel
{
OWSAssertIsOnMainThread();
self.isComplete = YES;
}
- (void)succeed
{
OWSLogInfo(@"");
dispatch_async(dispatch_get_main_queue(), ^{
if (self.isComplete) {
OWSAssertDebug(!self.hasSucceeded);
return;
}
self.isComplete = YES;
// There's a lot of asynchrony in these backup jobs;
// ensure we only end up finishing these jobs once.
OWSAssertDebug(!self.hasSucceeded);
self.hasSucceeded = YES;
[self.delegate backupJobDidSucceed:self];
});
}
- (void)failWithErrorDescription:(NSString *)description
{
[self failWithError:OWSErrorWithCodeDescription(OWSErrorCodeImportBackupFailed, description)];
}
- (void)failWithError:(NSError *)error
{
OWSFailDebug(@"%@", error);
dispatch_async(dispatch_get_main_queue(), ^{
OWSAssertDebug(!self.hasSucceeded);
if (self.isComplete) {
return;
}
self.isComplete = YES;
[self.delegate backupJobDidFail:self error:error];
});
}
- (void)updateProgressWithDescription:(nullable NSString *)description progress:(nullable NSNumber *)progress
{
OWSLogInfo(@"");
dispatch_async(dispatch_get_main_queue(), ^{
if (self.isComplete) {
return;
}
[self.delegate backupJobDidUpdate:self description:description progress:progress];
});
}
#pragma mark - Manifest
- (AnyPromise *)downloadAndProcessManifestWithBackupIO:(OWSBackupIO *)backupIO
{
OWSAssertDebug(backupIO);
OWSLogVerbose(@"");
if (self.isComplete) {
// Job was aborted.
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup job no longer active.")];
}
return
[OWSBackupAPI downloadManifestFromCloudObjcWithRecipientId:self.recipientId].thenInBackground(^(NSData *data) {
return [self processManifest:data backupIO:backupIO];
});
}
- (AnyPromise *)processManifest:(NSData *)manifestDataEncrypted backupIO:(OWSBackupIO *)backupIO
{
OWSAssertDebug(manifestDataEncrypted.length > 0);
OWSAssertDebug(backupIO);
if (self.isComplete) {
// Job was aborted.
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup job no longer active.")];
}
OWSLogVerbose(@"");
NSData *_Nullable manifestDataDecrypted =
[backupIO decryptDataAsData:manifestDataEncrypted encryptionKey:self.delegate.backupEncryptionKey];
if (!manifestDataDecrypted) {
OWSFailDebug(@"Could not decrypt manifest.");
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Could not decrypt manifest.")];
}
NSError *error;
NSDictionary<NSString *, id> *_Nullable json =
[NSJSONSerialization JSONObjectWithData:manifestDataDecrypted options:0 error:&error];
if (![json isKindOfClass:[NSDictionary class]]) {
OWSFailDebug(@"Could not download manifest.");
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Could not download manifest.")];
}
OWSLogVerbose(@"json: %@", json);
NSArray<OWSBackupFragment *> *_Nullable databaseItems =
[self parseManifestItems:json key:kOWSBackup_ManifestKey_DatabaseFiles];
if (!databaseItems) {
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"No database items in manifest.")];
}
NSArray<OWSBackupFragment *> *_Nullable attachmentsItems =
[self parseManifestItems:json key:kOWSBackup_ManifestKey_AttachmentFiles];
if (!attachmentsItems) {
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"No attachment items in manifest.")];
}
NSArray<OWSBackupFragment *> *_Nullable localProfileAvatarItems;
if ([self parseManifestItem:json key:kOWSBackup_ManifestKey_LocalProfileAvatar]) {
localProfileAvatarItems = [self parseManifestItems:json key:kOWSBackup_ManifestKey_LocalProfileAvatar];
}
NSString *_Nullable localProfileName = [self parseManifestItem:json key:kOWSBackup_ManifestKey_LocalProfileName];
OWSBackupManifestContents *contents = [OWSBackupManifestContents new];
contents.databaseItems = databaseItems;
contents.attachmentsItems = attachmentsItems;
contents.localProfileAvatarItem = localProfileAvatarItems.firstObject;
if ([localProfileName isKindOfClass:[NSString class]]) {
contents.localProfileName = localProfileName;
} else if (localProfileName) {
OWSFailDebug(@"Invalid localProfileName: %@", [localProfileName class]);
}
return [AnyPromise promiseWithValue:contents];
}
- (nullable id)parseManifestItem:(id)json key:(NSString *)key
{
OWSAssertDebug(json);
OWSAssertDebug(key.length);
if (![json isKindOfClass:[NSDictionary class]]) {
OWSFailDebug(@"manifest has invalid data.");
return nil;
}
id _Nullable value = json[key];
return value;
}
- (nullable NSArray<OWSBackupFragment *> *)parseManifestItems:(id)json key:(NSString *)key
{
OWSAssertDebug(json);
OWSAssertDebug(key.length);
if (![json isKindOfClass:[NSDictionary class]]) {
OWSFailDebug(@"manifest has invalid data.");
return nil;
}
NSArray *itemMaps = json[key];
if (![itemMaps isKindOfClass:[NSArray class]]) {
OWSFailDebug(@"manifest has invalid data.");
return nil;
}
NSMutableArray<OWSBackupFragment *> *items = [NSMutableArray new];
for (NSDictionary *itemMap in itemMaps) {
if (![itemMap isKindOfClass:[NSDictionary class]]) {
OWSFailDebug(@"manifest has invalid item.");
return nil;
}
NSString *_Nullable recordName = itemMap[kOWSBackup_ManifestKey_RecordName];
NSString *_Nullable encryptionKeyString = itemMap[kOWSBackup_ManifestKey_EncryptionKey];
NSString *_Nullable relativeFilePath = itemMap[kOWSBackup_ManifestKey_RelativeFilePath];
NSString *_Nullable attachmentId = itemMap[kOWSBackup_ManifestKey_AttachmentId];
NSNumber *_Nullable uncompressedDataLength = itemMap[kOWSBackup_ManifestKey_DataSize];
if (![recordName isKindOfClass:[NSString class]]) {
OWSFailDebug(@"manifest has invalid recordName: %@.", recordName);
return nil;
}
if (![encryptionKeyString isKindOfClass:[NSString class]]) {
OWSFailDebug(@"manifest has invalid encryptionKey.");
return nil;
}
// relativeFilePath is an optional field.
if (relativeFilePath && ![relativeFilePath isKindOfClass:[NSString class]]) {
OWSLogDebug(@"manifest has invalid relativeFilePath: %@.", relativeFilePath);
OWSFailDebug(@"manifest has invalid relativeFilePath");
return nil;
}
// attachmentId is an optional field.
if (attachmentId && ![attachmentId isKindOfClass:[NSString class]]) {
OWSLogDebug(@"manifest has invalid attachmentId: %@.", attachmentId);
OWSFailDebug(@"manifest has invalid attachmentId");
return nil;
}
NSData *_Nullable encryptionKey = [NSData dataFromBase64String:encryptionKeyString];
if (!encryptionKey) {
OWSFailDebug(@"manifest has corrupt encryptionKey");
return nil;
}
// uncompressedDataLength is an optional field.
if (uncompressedDataLength && ![uncompressedDataLength isKindOfClass:[NSNumber class]]) {
OWSFailDebug(@"manifest has invalid uncompressedDataLength: %@.", uncompressedDataLength);
return nil;
}
OWSBackupFragment *item = [[OWSBackupFragment alloc] initWithUniqueId:recordName];
item.recordName = recordName;
item.encryptionKey = encryptionKey;
item.relativeFilePath = relativeFilePath;
item.attachmentId = attachmentId;
item.uncompressedDataLength = uncompressedDataLength;
[items addObject:item];
}
return items;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,176 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
import SignalUtilitiesKit
@objc(OWSBackupLazyRestore)
public class BackupLazyRestore: NSObject {
// MARK: - Dependencies
private var backup: OWSBackup {
return AppEnvironment.shared.backup
}
private var primaryStorage: OWSPrimaryStorage {
return SSKEnvironment.shared.primaryStorage
}
private var tsAccountManager: TSAccountManager {
return TSAccountManager.sharedInstance()
}
// MARK: -
private var isRunning = false
private var isComplete = false
@objc
public required override init() {
super.init()
SwiftSingletons.register(self)
AppReadiness.runNowOrWhenAppDidBecomeReady {
self.runIfNecessary()
}
NotificationCenter.default.addObserver(forName: .OWSApplicationDidBecomeActive, object: nil, queue: nil) { _ in
self.runIfNecessary()
}
NotificationCenter.default.addObserver(forName: .RegistrationStateDidChange, object: nil, queue: nil) { _ in
self.runIfNecessary()
}
NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { _ in
self.runIfNecessary()
}
NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { _ in
self.runIfNecessary()
}
NotificationCenter.default.addObserver(forName: NSNotification.Name(NSNotificationNameBackupStateDidChange), object: nil, queue: nil) { _ in
self.runIfNecessary()
}
}
// MARK: -
private let backgroundQueue = DispatchQueue.global(qos: .background)
@objc
public func clearCompleteAndRunIfNecessary() {
AssertIsOnMainThread()
isComplete = false
runIfNecessary()
}
@objc
public func isBackupImportInProgress() -> Bool {
return backup.backupImportState == .inProgress
}
@objc
public func runIfNecessary() {
AssertIsOnMainThread()
guard !CurrentAppContext().isRunningTests else {
return
}
guard AppReadiness.isAppReady() else {
return
}
guard CurrentAppContext().isMainAppAndActive else {
return
}
guard tsAccountManager.isRegisteredAndReady() else {
return
}
guard !isBackupImportInProgress() else {
return
}
guard !isRunning, !isComplete else {
return
}
isRunning = true
backgroundQueue.async {
self.restoreAttachments()
}
}
private func restoreAttachments() {
let temporaryDirectory = OWSTemporaryDirectory()
let jobTempDirPath = (temporaryDirectory as NSString).appendingPathComponent(NSUUID().uuidString)
guard OWSFileSystem.ensureDirectoryExists(jobTempDirPath) else {
Logger.error("could not create temp directory.")
complete(errorCount: 1)
return
}
let backupIO = OWSBackupIO(jobTempDirPath: jobTempDirPath)
let attachmentIds = backup.attachmentIdsForLazyRestore()
guard attachmentIds.count > 0 else {
Logger.info("No attachments need lazy restore.")
complete(errorCount: 0)
return
}
Logger.info("Lazy restoring \(attachmentIds.count) attachments.")
tryToRestoreNextAttachment(attachmentIds: attachmentIds, errorCount: 0, backupIO: backupIO)
}
private func tryToRestoreNextAttachment(attachmentIds: [String], errorCount: UInt, backupIO: OWSBackupIO) {
guard !isBackupImportInProgress() else {
Logger.verbose("A backup import is in progress; abort.")
complete(errorCount: errorCount + 1)
return
}
var attachmentIdsCopy = attachmentIds
guard let attachmentId = attachmentIdsCopy.popLast() else {
// This job is done.
Logger.verbose("job is done.")
complete(errorCount: errorCount)
return
}
guard let attachmentPointer = TSAttachment.fetch(uniqueId: attachmentId) as? TSAttachmentPointer else {
Logger.warn("could not load attachment.")
// Not necessarily an error.
// The attachment might have been deleted since the job began.
// Continue trying to restore the other attachments.
tryToRestoreNextAttachment(attachmentIds: attachmentIds, errorCount: errorCount + 1, backupIO: backupIO)
return
}
backup.lazyRestoreAttachment(attachmentPointer,
backupIO: backupIO)
.done(on: self.backgroundQueue) { _ in
Logger.info("Restored attachment.")
// Continue trying to restore the other attachments.
self.tryToRestoreNextAttachment(attachmentIds: attachmentIdsCopy, errorCount: errorCount, backupIO: backupIO)
}.catch(on: self.backgroundQueue) { (error) in
Logger.error("Could not restore attachment: \(error)")
// Continue trying to restore the other attachments.
self.tryToRestoreNextAttachment(attachmentIds: attachmentIdsCopy, errorCount: errorCount + 1, backupIO: backupIO)
}.retainUntilComplete()
}
private func complete(errorCount: UInt) {
Logger.verbose("")
DispatchQueue.main.async {
self.isRunning = false
if errorCount == 0 {
self.isComplete = true
}
}
}
}

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/OWSTableViewController.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSBackupSettingsViewController : OWSTableViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -275,10 +275,6 @@ NS_ASSUME_NONNULL_BEGIN
selector:@selector(typingIndicatorStateDidChange:)
name:[OWSTypingIndicatorsImpl typingIndicatorStateDidChange]
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(profileWhitelistDidChange:)
name:kNSNotificationName_ProfileWhitelistDidChange
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(blockListDidChange:)
name:NSNotification.contactBlockedStateChanged
@ -289,14 +285,6 @@ NS_ASSUME_NONNULL_BEGIN
object:nil];
}
- (void)profileWhitelistDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
self.conversationProfileState = nil;
[self updateForTransientItems];
}
- (void)localProfileDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();

View File

@ -113,7 +113,7 @@ public class LongTextViewController: OWSViewController {
// MARK: - Create Views
private func createViews() {
view.backgroundColor = Theme.backgroundColor
view.backgroundColor = Colors.navigationBarBackground
let messageTextView = OWSTextView()
self.messageTextView = messageTextView

View File

@ -3,6 +3,7 @@
//
import Foundation
import SessionUIKit
@objc(OWSMediaView)
public class MediaView: UIView {
@ -149,7 +150,7 @@ public class MediaView: UIView {
configure(forError: .missing)
return
}
backgroundColor = (Theme.isDarkThemeEnabled ? .ows_gray90 : .ows_gray05)
backgroundColor = (isDarkMode ? .ows_gray90 : .ows_gray05)
let loader = MediaLoaderView()
addSubview(loader)
loader.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.bottom, UIView.HorizontalEdge.right ], to: self)
@ -352,7 +353,7 @@ public class MediaView: UIView {
}
private func configure(forError error: MediaError) {
backgroundColor = (Theme.isDarkThemeEnabled ? .ows_gray90 : .ows_gray05)
backgroundColor = (isDarkMode ? .ows_gray90 : .ows_gray05)
let icon: UIImage
switch error {
case .failed:

View File

@ -270,7 +270,7 @@ class GifPickerCell: UICollectionViewCell {
private func clearViewState() {
imageView?.image = nil
self.backgroundColor = (Theme.isDarkThemeEnabled
self.backgroundColor = (isDarkMode
? UIColor(white: 0.25, alpha: 1.0)
: UIColor(white: 0.95, alpha: 1.0))
}

View File

@ -3,10 +3,10 @@
//
import Foundation
import SignalUtilitiesKit
import Reachability
import SignalUtilitiesKit
import PromiseKit
import SessionUIKit
@objc
protocol GifPickerViewControllerDelegate: class {
@ -234,7 +234,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
private func createErrorLabel(text: String) -> UILabel {
let label = UILabel()
label.text = text
label.textColor = Theme.primaryColor
label.textColor = Colors.text
label.font = UIFont.ows_mediumFont(withSize: 20)
label.textAlignment = .center
label.numberOfLines = 0

View File

@ -15,6 +15,7 @@
#import <MediaPlayer/MediaPlayer.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SessionUtilitiesKit/NSData+Image.h>
#import <SessionUIKit/SessionUIKit.h>
#import <YYImage/YYImage.h>
NS_ASSUME_NONNULL_BEGIN
@ -200,18 +201,18 @@ NS_ASSUME_NONNULL_BEGIN
self.mediaView = animatedView;
} else {
self.mediaView = [UIView new];
self.mediaView.backgroundColor = Theme.offBackgroundColor;
self.mediaView.backgroundColor = LKColors.unimportant;
}
} else if (!self.image) {
// Still loading thumbnail.
self.mediaView = [UIView new];
self.mediaView.backgroundColor = Theme.offBackgroundColor;
self.mediaView.backgroundColor = LKColors.unimportant;
} else if (self.isVideo) {
if (self.attachmentStream.isValidVideo) {
self.mediaView = [self buildVideoPlayerView];
} else {
self.mediaView = [UIView new];
self.mediaView.backgroundColor = Theme.offBackgroundColor;
self.mediaView.backgroundColor = LKColors.unimportant;
}
} else {
// Present the static image using standard UIImageView

View File

@ -4,6 +4,7 @@
import UIKit
import PromiseKit
import SessionUIKit
// Objc wrapper for the MediaGalleryItem struct
@objc
@ -280,7 +281,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
lazy var shareBarButton: UIBarButtonItem = {
let shareBarButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(didPressShare))
shareBarButton.tintColor = Theme.darkThemePrimaryColor
shareBarButton.tintColor = Colors.text
return shareBarButton
}()
@ -288,7 +289,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
let deleteBarButton = UIBarButtonItem(barButtonSystemItem: .trash,
target: self,
action: #selector(didPressDelete))
deleteBarButton.tintColor = Theme.darkThemePrimaryColor
deleteBarButton.tintColor = Colors.text
return deleteBarButton
}()
@ -298,14 +299,14 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
lazy var videoPlayBarButton: UIBarButtonItem = {
let videoPlayBarButton = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(didPressPlayBarButton))
videoPlayBarButton.tintColor = Theme.darkThemePrimaryColor
videoPlayBarButton.tintColor = Colors.text
return videoPlayBarButton
}()
lazy var videoPauseBarButton: UIBarButtonItem = {
let videoPauseBarButton = UIBarButtonItem(barButtonSystemItem: .pause, target: self, action:
#selector(didPressPauseBarButton))
videoPauseBarButton.tintColor = Theme.darkThemePrimaryColor
videoPauseBarButton.tintColor = Colors.text
return videoPauseBarButton
}()

View File

@ -3,6 +3,8 @@
//
import Foundation
import SessionUIKit
import UIKit
public protocol MediaTileViewControllerDelegate: class {
func mediaTileViewController(_ viewController: MediaTileViewController, didTapView tappedView: UIView, mediaGalleryItem: MediaGalleryItem)
@ -75,7 +77,7 @@ public class MediaTileViewController: UICollectionViewController, MediaGalleryDa
let deleteButton = UIBarButtonItem(barButtonSystemItem: .trash,
target: self,
action: #selector(didPressDelete))
deleteButton.tintColor = Theme.darkThemeNavbarIconColor
deleteButton.tintColor = Colors.text
return deleteButton
}()
@ -823,16 +825,16 @@ private class MediaGallerySectionHeader: UICollectionReusableView {
override init(frame: CGRect) {
label = UILabel()
label.textColor = Theme.darkThemePrimaryColor
label.textColor = Colors.text
let blurEffect = Theme.darkThemeBarBlurEffect
let blurEffect = UIBlurEffect(style: .dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
super.init(frame: frame)
self.backgroundColor = isLightMode ? Colors.cellBackground : Theme.darkThemeNavbarBackgroundColor.withAlphaComponent(OWSNavigationBar.backgroundBlurMutingFactor)
self.backgroundColor = isLightMode ? Colors.cellBackground : UIColor.ows_black.withAlphaComponent(OWSNavigationBar.backgroundBlurMutingFactor)
self.addSubview(blurEffectView)
self.addSubview(label)
@ -871,7 +873,7 @@ private class MediaGalleryStaticHeader: UICollectionViewCell {
addSubview(label)
label.textColor = Theme.darkThemePrimaryColor
label.textColor = Colors.text
label.textAlignment = .center
label.numberOfLines = 0
label.autoPinEdgesToSuperviewMargins(with: UIEdgeInsets(top: 0, leading: Values.largeSpacing, bottom: 0, trailing: Values.largeSpacing))

View File

@ -2,6 +2,9 @@
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import UIKit
import SessionUIKit
public enum PhotoGridItemType {
case photo, animated, video
}
@ -29,7 +32,7 @@ public class PhotoGridViewCell: UICollectionViewCell {
private static let animatedBadgeImage = #imageLiteral(resourceName: "ic_gallery_badge_gif")
private static let selectedBadgeImage = #imageLiteral(resourceName: "selected_blue_circle")
public var loadingColor = Theme.offBackgroundColor
public var loadingColor = Colors.unimportant
override public var isSelected: Bool {
didSet {

View File

@ -4,8 +4,6 @@
#import "AppDelegate.h"
#import "MainAppContext.h"
#import "OWSBackup.h"
#import "OWSOrphanDataCleaner.h"
#import "OWSScreenLockUI.h"
#import "Session-Swift.h"
#import "SignalApp.h"
@ -101,11 +99,6 @@ static NSTimeInterval launchStartedAt;
return Environment.shared.windowManager;
}
- (OWSBackup *)backup
{
return AppEnvironment.shared.backup;
}
- (OWSNotificationPresenter *)notificationPresenter
{
return AppEnvironment.shared.notificationPresenter;
@ -557,11 +550,7 @@ static NSTimeInterval launchStartedAt;
UIViewController *rootViewController;
BOOL navigationBarHidden = NO;
if ([self.tsAccountManager isRegistered]) {
if (self.backup.hasPendingRestoreDecision) {
rootViewController = [BackupRestoreViewController new];
} else {
rootViewController = [HomeVC new];
}
rootViewController = [HomeVC new];
} else {
rootViewController = [LandingVC new];
navigationBarHidden = NO;

View File

@ -37,9 +37,6 @@ import SignalUtilitiesKit
@objc
public var pushRegistrationManager: PushRegistrationManager
@objc
public var backup: OWSBackup
@objc
public var fileLogger: DDFileLogger
@ -52,16 +49,11 @@ import SignalUtilitiesKit
return _userNotificationActionHandler as! UserNotificationActionHandler
}
@objc
public var backupLazyRestore: BackupLazyRestore
private override init() {
self.accountManager = AccountManager()
self.callManager = SessionCallManager()
self.notificationPresenter = NotificationPresenter()
self.pushRegistrationManager = PushRegistrationManager()
self.backup = OWSBackup()
self.backupLazyRestore = BackupLazyRestore()
self._userNotificationActionHandler = UserNotificationActionHandler()
self.fileLogger = DDFileLogger()

View File

@ -19,8 +19,6 @@
#import "NotificationSettingsViewController.h"
#import "OWSAnyTouchGestureRecognizer.h"
#import "OWSAudioPlayer.h"
#import "OWSBackup.h"
#import "OWSBackupIO.h"
#import "OWSBezierPathView.h"
#import "OWSConversationSettingsViewController.h"
#import "OWSDatabaseMigration.h"

View File

@ -23,7 +23,7 @@ class DismissInputBar: UIToolbar {
self.items = [spacer, dismissButton]
self.isTranslucent = false
self.isOpaque = true
self.barTintColor = Theme.toolbarBackgroundColor
self.barTintColor = UIColor.lokiDarkestGray()
self.autoresizingMask = .flexibleHeight
self.translatesAutoresizingMaskIntoConstraints = false

View File

@ -3,6 +3,7 @@
//
import Foundation
import SessionUIKit
class ReminderView: UIView {
@ -70,9 +71,9 @@ class ReminderView: UIView {
iconColor = UIColor.ows_gray60
case .explanation:
// TODO: Theme, review with design.
self.backgroundColor = Theme.offBackgroundColor
textColor = Theme.primaryColor
iconColor = Theme.secondaryColor
self.backgroundColor = Colors.unimportant
textColor = Colors.text
iconColor = Colors.separator
}
self.clipsToBounds = true

View File

@ -1,23 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
// Notes:
//
// * On disk, we only bother cleaning up files, not directories.
@interface OWSOrphanDataCleaner : NSObject
- (instancetype)init NS_UNAVAILABLE;
// This is exposed for the debug UI.
+ (void)auditAndCleanup:(BOOL)shouldCleanup;
// This is exposed for the tests.
+ (void)auditAndCleanup:(BOOL)shouldCleanup completion:(dispatch_block_t)completion;
+ (void)auditOnLaunchIfNecessary;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,737 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSOrphanDataCleaner.h"
#import "DateUtil.h"
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalUtilitiesKit/OWSProfileManager.h>
#import <SessionMessagingKit/OWSUserProfile.h>
#import <SessionMessagingKit/AppReadiness.h>
#import <SignalUtilitiesKit/AppVersion.h>
#import <SessionUtilitiesKit/SessionUtilitiesKit.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>
#import <SessionMessagingKit/OWSPrimaryStorage.h>
#import <SessionMessagingKit/TSAttachmentStream.h>
#import <SessionMessagingKit/TSInteraction.h>
#import <SessionMessagingKit/TSMessage.h>
#import <SessionMessagingKit/TSQuotedMessage.h>
#import <SessionMessagingKit/TSThread.h>
#import <SessionMessagingKit/YapDatabaseTransaction+OWS.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <YapDatabase/YapDatabase.h>
NS_ASSUME_NONNULL_BEGIN
// LOG_ALL_FILE_PATHS can be used to determine if there are other kinds of files
// that we're not cleaning up.
//#define LOG_ALL_FILE_PATHS
#define ENABLE_ORPHAN_DATA_CLEANER
NSString *const OWSOrphanDataCleaner_Collection = @"OWSOrphanDataCleaner_Collection";
NSString *const OWSOrphanDataCleaner_LastCleaningVersionKey = @"OWSOrphanDataCleaner_LastCleaningVersionKey";
NSString *const OWSOrphanDataCleaner_LastCleaningDateKey = @"OWSOrphanDataCleaner_LastCleaningDateKey";
@interface OWSOrphanData : NSObject
@property (nonatomic) NSSet<NSString *> *interactionIds;
@property (nonatomic) NSSet<NSString *> *attachmentIds;
@property (nonatomic) NSSet<NSString *> *filePaths;
@end
#pragma mark -
@implementation OWSOrphanData
@end
#pragma mark -
typedef void (^OrphanDataBlock)(OWSOrphanData *);
@implementation OWSOrphanDataCleaner
// Unlike CurrentAppContext().isMainAppAndActive, this method can be safely
// invoked off the main thread.
+ (BOOL)isMainAppAndActive
{
return CurrentAppContext().reportedApplicationState == UIApplicationStateActive;
}
+ (void)printPaths:(NSArray<NSString *> *)paths label:(NSString *)label
{
for (NSString *path in [paths sortedArrayUsingSelector:@selector(compare:)]) {
OWSLogDebug(@"%@: %@", label, path);
}
}
+ (long long)fileSizeOfFilePath:(NSString *)filePath
{
NSError *error;
NSNumber *fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error][NSFileSize];
if (error) {
if ([error.domain isEqualToString:NSCocoaErrorDomain] && error.code == 260) {
OWSLogWarn(@"can't find size of missing file.");
OWSLogDebug(@"can't find size of missing file: %@", filePath);
} else {
OWSFailDebug(@"attributesOfItemAtPath: %@ error: %@", filePath, error);
}
return 0;
}
return fileSize.longLongValue;
}
+ (nullable NSNumber *)fileSizeOfFilePathsSafe:(NSArray<NSString *> *)filePaths
{
long long result = 0;
for (NSString *filePath in filePaths) {
if (!self.isMainAppAndActive) {
return nil;
}
result += [self fileSizeOfFilePath:filePath];
}
return @(result);
}
+ (nullable NSSet<NSString *> *)filePathsInDirectorySafe:(NSString *)dirPath
{
NSMutableSet *filePaths = [NSMutableSet new];
if (![[NSFileManager defaultManager] fileExistsAtPath:dirPath]) {
return filePaths;
}
NSError *error;
NSArray<NSString *> *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:&error];
if (error) {
OWSFailDebug(@"contentsOfDirectoryAtPath error: %@", error);
return [NSSet new];
}
for (NSString *fileName in fileNames) {
if (!self.isMainAppAndActive) {
return nil;
}
NSString *filePath = [dirPath stringByAppendingPathComponent:fileName];
BOOL isDirectory;
[[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
if (isDirectory) {
NSSet<NSString *> *_Nullable dirPaths = [self filePathsInDirectorySafe:filePath];
if (!dirPaths) {
return nil;
}
[filePaths unionSet:dirPaths];
} else {
[filePaths addObject:filePath];
}
}
return filePaths;
}
// This method finds (but does not delete):
//
// * Orphan TSInteractions (with no thread).
// * Orphan TSAttachments (with no message).
// * Orphan attachment files (with no corresponding TSAttachment).
// * Orphan profile avatars.
// * Temporary files (all).
//
// It also finds (we don't clean these up).
//
// * Missing attachment files (cannot be cleaned up).
// These are attachments which have no file on disk. They should be extremely rare -
// the only cases I have seen are probably due to debugging.
// They can't be cleaned up - we don't want to delete the TSAttachmentStream or
// its corresponding message. Better that the broken message shows up in the
// conversation view.
+ (void)findOrphanDataWithRetries:(NSInteger)remainingRetries
databaseConnection:(YapDatabaseConnection *)databaseConnection
success:(OrphanDataBlock)success
failure:(dispatch_block_t)failure
{
OWSAssertDebug(databaseConnection);
if (remainingRetries < 1) {
OWSLogInfo(@"Aborting orphan data search.");
dispatch_async(self.workQueue, ^{
failure();
});
return;
}
// Wait until the app is active...
[CurrentAppContext() runNowOrWhenMainAppIsActive:^{
// ...but perform the work off the main thread.
dispatch_async(self.workQueue, ^{
OWSOrphanData *_Nullable orphanData = [self findOrphanDataSync:databaseConnection];
if (orphanData) {
success(orphanData);
} else {
[self findOrphanDataWithRetries:remainingRetries - 1
databaseConnection:databaseConnection
success:success
failure:failure];
}
});
}];
}
// Returns nil on failure, usually indicating that the search
// aborted due to the app resigning active. This method is extremely careful to
// abort if the app resigns active, in order to avoid 0xdead10cc crashes.
+ (nullable OWSOrphanData *)findOrphanDataSync:(YapDatabaseConnection *)databaseConnection
{
OWSAssertDebug(databaseConnection);
__block BOOL shouldAbort = NO;
#ifdef LOG_ALL_FILE_PATHS
{
NSString *documentDirPath = [OWSFileSystem appDocumentDirectoryPath];
NSArray<NSString *> *_Nullable allDocumentFilePaths =
[self filePathsInDirectorySafe:documentDirPath].allObjects;
allDocumentFilePaths = [allDocumentFilePaths sortedArrayUsingSelector:@selector(compare:)];
NSString *attachmentsFolder = [TSAttachmentStream attachmentsFolder];
for (NSString *filePath in allDocumentFilePaths) {
if ([filePath hasPrefix:attachmentsFolder]) {
continue;
}
OWSLogVerbose(@"non-attachment file: %@", filePath);
}
}
{
NSString *documentDirPath = [OWSFileSystem appSharedDataDirectoryPath];
NSArray<NSString *> *_Nullable allDocumentFilePaths =
[self filePathsInDirectorySafe:documentDirPath].allObjects;
allDocumentFilePaths = [allDocumentFilePaths sortedArrayUsingSelector:@selector(compare:)];
NSString *attachmentsFolder = [TSAttachmentStream attachmentsFolder];
for (NSString *filePath in allDocumentFilePaths) {
if ([filePath hasPrefix:attachmentsFolder]) {
continue;
}
OWSLogVerbose(@"non-attachment file: %@", filePath);
}
}
#endif
// We treat _all_ temp files as orphan files. This is safe
// because temp files only need to be retained for the
// a single launch of the app. Since our "date threshold"
// for deletion is relative to the current launch time,
// all temp files currently in use should be safe.
NSArray<NSString *> *_Nullable tempFilePaths = [self getTempFilePaths];
if (!tempFilePaths || !self.isMainAppAndActive) {
return nil;
}
#ifdef LOG_ALL_FILE_PATHS
{
NSDateFormatter *dateFormatter = [NSDateFormatter new];
[dateFormatter setDateStyle:NSDateFormatterLongStyle];
[dateFormatter setTimeStyle:NSDateFormatterLongStyle];
tempFilePaths = [tempFilePaths sortedArrayUsingSelector:@selector(compare:)];
for (NSString *filePath in tempFilePaths) {
NSError *error;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];
if (!attributes || error) {
OWSLogDebug(@"Could not get attributes of file at: %@", filePath);
OWSFailDebug(@"Could not get attributes of file");
continue;
}
OWSLogVerbose(
@"temp file: %@, %@", filePath, [dateFormatter stringFromDate:attributes.fileModificationDate]);
}
}
#endif
NSString *legacyAttachmentsDirPath = TSAttachmentStream.legacyAttachmentsDirPath;
NSString *sharedDataAttachmentsDirPath = TSAttachmentStream.sharedDataAttachmentsDirPath;
NSSet<NSString *> *_Nullable legacyAttachmentFilePaths = [self filePathsInDirectorySafe:legacyAttachmentsDirPath];
if (!legacyAttachmentFilePaths || !self.isMainAppAndActive) {
return nil;
}
NSSet<NSString *> *_Nullable sharedDataAttachmentFilePaths =
[self filePathsInDirectorySafe:sharedDataAttachmentsDirPath];
if (!sharedDataAttachmentFilePaths || !self.isMainAppAndActive) {
return nil;
}
NSString *legacyProfileAvatarsDirPath = OWSUserProfile.legacyProfileAvatarsDirPath;
NSString *sharedDataProfileAvatarsDirPath = OWSUserProfile.sharedDataProfileAvatarsDirPath;
NSSet<NSString *> *_Nullable legacyProfileAvatarsFilePaths =
[self filePathsInDirectorySafe:legacyProfileAvatarsDirPath];
if (!legacyProfileAvatarsFilePaths || !self.isMainAppAndActive) {
return nil;
}
NSSet<NSString *> *_Nullable sharedDataProfileAvatarFilePaths =
[self filePathsInDirectorySafe:sharedDataProfileAvatarsDirPath];
if (!sharedDataProfileAvatarFilePaths || !self.isMainAppAndActive) {
return nil;
}
NSMutableSet<NSString *> *allOnDiskFilePaths = [NSMutableSet new];
[allOnDiskFilePaths unionSet:legacyAttachmentFilePaths];
[allOnDiskFilePaths unionSet:sharedDataAttachmentFilePaths];
[allOnDiskFilePaths unionSet:legacyProfileAvatarsFilePaths];
[allOnDiskFilePaths unionSet:sharedDataProfileAvatarFilePaths];
[allOnDiskFilePaths addObjectsFromArray:tempFilePaths];
NSSet<NSString *> *profileAvatarFilePaths = [OWSUserProfile allProfileAvatarFilePaths];
if (!self.isMainAppAndActive) {
return nil;
}
NSNumber *_Nullable totalFileSize = [self fileSizeOfFilePathsSafe:allOnDiskFilePaths.allObjects];
if (!totalFileSize || !self.isMainAppAndActive) {
return nil;
}
NSUInteger fileCount = allOnDiskFilePaths.count;
// Attachments
__block int attachmentStreamCount = 0;
NSMutableSet<NSString *> *allAttachmentFilePaths = [NSMutableSet new];
NSMutableSet<NSString *> *allAttachmentIds = [NSMutableSet new];
// Threads
__block NSSet *threadIds;
// Messages
NSMutableSet<NSString *> *orphanInteractionIds = [NSMutableSet new];
NSMutableSet<NSString *> *allMessageAttachmentIds = [NSMutableSet new];
[databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[transaction
enumerateKeysAndObjectsInCollection:TSAttachmentStream.collection
usingBlock:^(NSString *key, TSAttachment *attachment, BOOL *stop) {
if (!self.isMainAppAndActive) {
shouldAbort = YES;
*stop = YES;
return;
}
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
return;
}
[allAttachmentIds addObject:attachment.uniqueId];
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
attachmentStreamCount++;
NSString *_Nullable filePath = [attachmentStream originalFilePath];
if (filePath) {
[allAttachmentFilePaths addObject:filePath];
} else {
OWSFailDebug(@"attachment has no file path.");
}
[allAttachmentFilePaths
addObjectsFromArray:attachmentStream.allThumbnailPaths];
}];
if (shouldAbort) {
return;
}
threadIds = [NSSet setWithArray:[transaction allKeysInCollection:TSThread.collection]];
[transaction
enumerateKeysAndObjectsInCollection:TSMessage.collection
usingBlock:^(NSString *key, TSInteraction *interaction, BOOL *stop) {
if (!self.isMainAppAndActive) {
shouldAbort = YES;
*stop = YES;
return;
}
if (interaction.uniqueThreadId.length < 1
|| ![threadIds containsObject:interaction.uniqueThreadId]) {
[orphanInteractionIds addObject:interaction.uniqueId];
}
if (![interaction isKindOfClass:[TSMessage class]]) {
return;
}
TSMessage *message = (TSMessage *)interaction;
[allMessageAttachmentIds addObjectsFromArray:message.allAttachmentIds];
}];
}];
if (shouldAbort) {
return nil;
}
OWSLogDebug(@"fileCount: %zu", fileCount);
OWSLogDebug(@"totalFileSize: %lld", totalFileSize.longLongValue);
OWSLogDebug(@"attachmentStreams: %d", attachmentStreamCount);
OWSLogDebug(@"attachmentStreams with file paths: %zu", allAttachmentFilePaths.count);
NSMutableSet<NSString *> *orphanFilePaths = [allOnDiskFilePaths mutableCopy];
[orphanFilePaths minusSet:allAttachmentFilePaths];
[orphanFilePaths minusSet:profileAvatarFilePaths];
NSMutableSet<NSString *> *missingAttachmentFilePaths = [allAttachmentFilePaths mutableCopy];
[missingAttachmentFilePaths minusSet:allOnDiskFilePaths];
OWSLogDebug(@"orphan file paths: %zu", orphanFilePaths.count);
OWSLogDebug(@"missing attachment file paths: %zu", missingAttachmentFilePaths.count);
[self printPaths:orphanFilePaths.allObjects label:@"orphan file paths"];
[self printPaths:missingAttachmentFilePaths.allObjects label:@"missing attachment file paths"];
OWSLogDebug(@"attachmentIds: %zu", allAttachmentIds.count);
OWSLogDebug(@"allMessageAttachmentIds: %zu", allMessageAttachmentIds.count);
NSMutableSet<NSString *> *orphanAttachmentIds = [allAttachmentIds mutableCopy];
[orphanAttachmentIds minusSet:allMessageAttachmentIds];
NSMutableSet<NSString *> *missingAttachmentIds = [allMessageAttachmentIds mutableCopy];
[missingAttachmentIds minusSet:allAttachmentIds];
OWSLogDebug(@"orphan attachmentIds: %zu", orphanAttachmentIds.count);
OWSLogDebug(@"missing attachmentIds: %zu", missingAttachmentIds.count);
OWSLogDebug(@"orphan interactions: %zu", orphanInteractionIds.count);
OWSOrphanData *result = [OWSOrphanData new];
result.interactionIds = [orphanInteractionIds copy];
result.attachmentIds = [orphanAttachmentIds copy];
result.filePaths = [orphanFilePaths copy];
return result;
}
+ (BOOL)shouldAuditOnLaunch:(YapDatabaseConnection *)databaseConnection {
OWSAssertIsOnMainThread();
#ifndef ENABLE_ORPHAN_DATA_CLEANER
return NO;
#endif
__block NSString *_Nullable lastCleaningVersion;
__block NSDate *_Nullable lastCleaningDate;
[databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
lastCleaningVersion = [transaction stringForKey:OWSOrphanDataCleaner_LastCleaningVersionKey
inCollection:OWSOrphanDataCleaner_Collection];
lastCleaningDate = [transaction dateForKey:OWSOrphanDataCleaner_LastCleaningDateKey
inCollection:OWSOrphanDataCleaner_Collection];
}];
// Clean up once per app version.
NSString *currentAppVersion = AppVersion.sharedInstance.currentAppVersion;
if (!lastCleaningVersion || ![lastCleaningVersion isEqualToString:currentAppVersion]) {
OWSLogVerbose(@"Performing orphan data cleanup; new version: %@.", currentAppVersion);
return YES;
}
// Clean up once per N days.
if (lastCleaningDate) {
#ifdef DEBUG
BOOL shouldAudit = [DateUtil dateIsOlderThanToday:lastCleaningDate];
#else
BOOL shouldAudit = [DateUtil dateIsOlderThanOneWeek:lastCleaningDate];
#endif
if (shouldAudit) {
OWSLogVerbose(@"Performing orphan data cleanup; time has passed.");
}
return shouldAudit;
}
// Has never audited before.
return NO;
}
+ (void)auditOnLaunchIfNecessary {
OWSAssertIsOnMainThread();
OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager];
YapDatabaseConnection *databaseConnection = [primaryStorage newDatabaseConnection];
if (![self shouldAuditOnLaunch:databaseConnection]) {
return;
}
// If we want to be cautious, we can disable orphan deletion using
// flag - the cleanup will just be a dry run with logging.
BOOL shouldRemoveOrphans = YES;
[self auditAndCleanup:shouldRemoveOrphans databaseConnection:databaseConnection completion:nil];
}
+ (void)auditAndCleanup:(BOOL)shouldRemoveOrphans
{
[self auditAndCleanup:shouldRemoveOrphans
completion:^ {
}];
}
+ (void)auditAndCleanup:(BOOL)shouldRemoveOrphans completion:(dispatch_block_t)completion
{
OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager];
YapDatabaseConnection *databaseConnection = [primaryStorage newDatabaseConnection];
[self auditAndCleanup:shouldRemoveOrphans databaseConnection:databaseConnection completion:completion];
}
// We use the lowest priority possible.
+ (dispatch_queue_t)workQueue
{
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
}
+ (void)auditAndCleanup:(BOOL)shouldRemoveOrphans
databaseConnection:(YapDatabaseConnection *)databaseConnection
completion:(nullable dispatch_block_t)completion
{
OWSAssertIsOnMainThread();
OWSAssertDebug(databaseConnection);
if (!AppReadiness.isAppReady) {
OWSFailDebug(@"can't audit orphan data until app is ready.");
return;
}
if (!CurrentAppContext().isMainApp) {
OWSFailDebug(@"can't audit orphan data in app extensions.");
return;
}
if (CurrentAppContext().isRunningTests) {
OWSLogVerbose(@"Ignoring audit orphan data in tests.");
return;
}
// Orphan cleanup has two risks:
//
// * As a long-running process that involves access to the
// shared data container, it could cause 0xdead10cc.
// * It could accidentally delete data still in use,
// e.g. a profile avatar which has been saved to disk
// but whose OWSUserProfile hasn't been saved yet.
//
// To prevent 0xdead10cc, the cleaner continually checks
// whether the app has resigned active. If so, it aborts.
// Each phase (search, re-search, processing) retries N times,
// then gives up until the next app launch.
//
// To prevent accidental data deletion, we take the following
// measures:
//
// * Only cleanup data of the following types (which should
// include all relevant app data): profile avatar,
// attachment, temporary files (including temporary
// attachments).
// * We don't delete any data created more recently than N seconds
// _before_ when the app launched. This prevents any stray data
// currently in use by the app from being accidentally cleaned
// up.
const NSInteger kMaxRetries = 3;
[self findOrphanDataWithRetries:kMaxRetries
databaseConnection:databaseConnection
success:^(OWSOrphanData *orphanData) {
[self processOrphans:orphanData
remainingRetries:kMaxRetries
databaseConnection:databaseConnection
shouldRemoveOrphans:shouldRemoveOrphans
success:^{
OWSLogInfo(@"Completed orphan data cleanup.");
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:AppVersion.sharedInstance.currentAppVersion
forKey:OWSOrphanDataCleaner_LastCleaningVersionKey
inCollection:OWSOrphanDataCleaner_Collection];
[transaction setDate:[NSDate new]
forKey:OWSOrphanDataCleaner_LastCleaningDateKey
inCollection:OWSOrphanDataCleaner_Collection];
}];
if (completion) {
completion();
}
}
failure:^{
OWSLogInfo(@"Aborting orphan data cleanup.");
if (completion) {
completion();
}
}];
}
failure:^{
OWSLogInfo(@"Aborting orphan data cleanup.");
if (completion) {
completion();
}
}];
}
// Returns NO on failure, usually indicating that orphan processing
// aborted due to the app resigning active. This method is extremely careful to
// abort if the app resigns active, in order to avoid 0xdead10cc crashes.
+ (void)processOrphans:(OWSOrphanData *)orphanData
remainingRetries:(NSInteger)remainingRetries
databaseConnection:(YapDatabaseConnection *)databaseConnection
shouldRemoveOrphans:(BOOL)shouldRemoveOrphans
success:(dispatch_block_t)success
failure:(dispatch_block_t)failure
{
OWSAssertDebug(databaseConnection);
OWSAssertDebug(orphanData);
if (remainingRetries < 1) {
OWSLogInfo(@"Aborting orphan data audit.");
dispatch_async(self.workQueue, ^{
failure();
});
return;
}
// Wait until the app is active...
[CurrentAppContext() runNowOrWhenMainAppIsActive:^{
// ...but perform the work off the main thread.
dispatch_async(self.workQueue, ^{
if ([self processOrphansSync:orphanData
databaseConnection:databaseConnection
shouldRemoveOrphans:shouldRemoveOrphans]) {
success();
return;
} else {
[self processOrphans:orphanData
remainingRetries:remainingRetries - 1
databaseConnection:databaseConnection
shouldRemoveOrphans:shouldRemoveOrphans
success:success
failure:failure];
}
});
}];
}
// Returns NO on failure, usually indicating that orphan processing
// aborted due to the app resigning active. This method is extremely careful to
// abort if the app resigns active, in order to avoid 0xdead10cc crashes.
+ (BOOL)processOrphansSync:(OWSOrphanData *)orphanData
databaseConnection:(YapDatabaseConnection *)databaseConnection
shouldRemoveOrphans:(BOOL)shouldRemoveOrphans
{
OWSAssertDebug(databaseConnection);
OWSAssertDebug(orphanData);
__block BOOL shouldAbort = NO;
// We need to avoid cleaning up new files that are still in the process of
// being created/written, so we don't clean up anything recent.
const NSTimeInterval kMinimumOrphanAgeSeconds = CurrentAppContext().isRunningTests ? 0.f : 15 * kMinuteInterval;
NSDate *appLaunchTime = CurrentAppContext().appLaunchTime;
NSTimeInterval thresholdTimestamp = appLaunchTime.timeIntervalSince1970 - kMinimumOrphanAgeSeconds;
NSDate *thresholdDate = [NSDate dateWithTimeIntervalSince1970:thresholdTimestamp];
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger interactionsRemoved = 0;
for (NSString *interactionId in orphanData.interactionIds) {
if (!self.isMainAppAndActive) {
shouldAbort = YES;
return;
}
TSInteraction *_Nullable interaction =
[TSInteraction fetchObjectWithUniqueID:interactionId transaction:transaction];
if (!interaction) {
// This could just be a race condition, but it should be very unlikely.
OWSLogWarn(@"Could not load interaction: %@", interactionId);
continue;
}
// Don't delete interactions which were created in the last N minutes.
NSDate *creationDate = [NSDate ows_dateWithMillisecondsSince1970:interaction.timestamp];
if ([creationDate isAfterDate:thresholdDate]) {
OWSLogInfo(@"Skipping orphan interaction due to age: %f", fabs(creationDate.timeIntervalSinceNow));
continue;
}
OWSLogInfo(@"Removing orphan message: %@", interaction.uniqueId);
interactionsRemoved++;
if (!shouldRemoveOrphans) {
continue;
}
[interaction removeWithTransaction:transaction];
}
OWSLogInfo(@"Deleted orphan interactions: %zu", interactionsRemoved);
NSUInteger attachmentsRemoved = 0;
for (NSString *attachmentId in orphanData.attachmentIds) {
if (!self.isMainAppAndActive) {
shouldAbort = YES;
return;
}
TSAttachment *_Nullable attachment =
[TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction];
if (!attachment) {
// This can happen on launch since we sync contacts/groups, especially if you have a lot of attachments
// to churn through, it's likely it's been deleted since starting this job.
OWSLogWarn(@"Could not load attachment: %@", attachmentId);
continue;
}
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
continue;
}
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
// Don't delete attachments which were created in the last N minutes.
NSDate *creationDate = attachmentStream.creationTimestamp;
if ([creationDate isAfterDate:thresholdDate]) {
OWSLogInfo(@"Skipping orphan attachment due to age: %f", fabs(creationDate.timeIntervalSinceNow));
continue;
}
OWSLogInfo(@"Removing orphan attachmentStream: %@", attachmentStream.uniqueId);
attachmentsRemoved++;
if (!shouldRemoveOrphans) {
continue;
}
[attachmentStream removeWithTransaction:transaction];
}
OWSLogInfo(@"Deleted orphan attachments: %zu", attachmentsRemoved);
}];
if (shouldAbort) {
return nil;
}
NSUInteger filesRemoved = 0;
NSArray<NSString *> *filePaths = [orphanData.filePaths.allObjects sortedArrayUsingSelector:@selector(compare:)];
for (NSString *filePath in filePaths) {
if (!self.isMainAppAndActive) {
return nil;
}
NSError *error;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];
if (!attributes || error) {
// This is fine; the file may have been deleted since we found it.
OWSLogWarn(@"Could not get attributes of file at: %@", filePath);
continue;
}
// Don't delete files which were created in the last N minutes.
NSDate *creationDate = attributes.fileModificationDate;
if ([creationDate isAfterDate:thresholdDate]) {
OWSLogInfo(@"Skipping file due to age: %f", fabs([creationDate timeIntervalSinceNow]));
continue;
}
OWSLogInfo(@"Deleting file: %@", filePath);
filesRemoved++;
if (!shouldRemoveOrphans) {
continue;
}
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
if (error) {
OWSLogDebug(@"Could not remove orphan file at: %@", filePath);
OWSFailDebug(@"Could not remove orphan file");
}
}
OWSLogInfo(@"Deleted orphan files: %zu", filesRemoved);
return YES;
}
+ (nullable NSArray<NSString *> *)getTempFilePaths
{
NSString *dir1 = OWSTemporaryDirectory();
NSArray<NSString *> *_Nullable paths1 = [[self filePathsInDirectorySafe:dir1].allObjects mutableCopy];
NSString *dir2 = OWSTemporaryDirectoryAccessibleAfterFirstAuth();
NSArray<NSString *> *_Nullable paths2 = [[self filePathsInDirectorySafe:dir2].allObjects mutableCopy];
if (paths1 && paths2) {
return [paths1 arrayByAddingObjectsFromArray:paths2];
} else {
return nil;
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -150,7 +150,6 @@ public final class OpenGroupManagerV2 : NSObject {
messageIDs.insert(interaction.uniqueId!)
messageTimestamps.insert(interaction.timestamp)
}
storage.updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, using: transaction)
Storage.shared.removeReceivedMessageTimestamps(messageTimestamps, using: transaction)
Storage.shared.removeLastMessageServerID(for: openGroup.room, on: openGroup.server, using: transaction)
Storage.shared.removeLastDeletionServerID(for: openGroup.room, on: openGroup.server, using: transaction)

View File

@ -16,12 +16,6 @@
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, OWSReceiptType) {
OWSReceiptType_Delivery,
OWSReceiptType_Read,
};
NSString *const kOutgoingDeliveryReceiptManagerCollection = @"kOutgoingDeliveryReceiptManagerCollection";
NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptManagerCollection";
@interface OWSOutgoingReceiptManager ()
@ -103,7 +97,7 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
}
NSMutableArray<AnyPromise *> *sendPromises = [NSMutableArray array];
[sendPromises addObjectsFromArray:[self sendReceiptsForReceiptType:OWSReceiptType_Read]];
[sendPromises addObjectsFromArray:[self sendReceipts]];
if (sendPromises.count < 1) {
// No work to do; abort.
@ -132,10 +126,8 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
});
}
- (NSArray<AnyPromise *> *)sendReceiptsForReceiptType:(OWSReceiptType)receiptType {
if (receiptType == OWSReceiptType_Delivery) { return @[]; } // Don't send delivery receipts
NSString *collection = [self collectionForReceiptType:receiptType];
- (NSArray<AnyPromise *> *)sendReceipts {
NSString *collection = kOutgoingReadReceiptManagerCollection;
NSMutableDictionary<NSString *, NSSet<NSNumber *> *> *queuedReceiptMap = [NSMutableDictionary new];
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
@ -170,7 +162,7 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
AnyPromise *promise = [SNMessageSender sendNonDurably:readReceipt inThread:thread usingTransaction:transaction]
.thenOn(self.serialQueue, ^(id object) {
[self dequeueReceiptsWithRecipientId:recipientId timestamps:timestampsAsSet receiptType:OWSReceiptType_Read];
[self dequeueReceiptsWithRecipientId:recipientId timestamps:timestampsAsSet];
});
[sendPromises addObject:promise];
}];
@ -179,22 +171,11 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
return [sendPromises copy];
}
- (void)enqueueDeliveryReceiptForEnvelope:(SNProtoEnvelope *)envelope
{
[self enqueueReceiptWithRecipientId:envelope.source
timestamp:envelope.timestamp
receiptType:OWSReceiptType_Delivery];
}
- (void)enqueueReadReceiptForEnvelope:(NSString *)messageAuthorId timestamp:(uint64_t)timestamp {
[self enqueueReceiptWithRecipientId:messageAuthorId timestamp:timestamp receiptType:OWSReceiptType_Read];
[self enqueueReceiptWithRecipientId:messageAuthorId timestamp:timestamp];
}
- (void)enqueueReceiptWithRecipientId:(NSString *)recipientId
timestamp:(uint64_t)timestamp
receiptType:(OWSReceiptType)receiptType {
NSString *collection = [self collectionForReceiptType:receiptType];
- (void)enqueueReceiptWithRecipientId:(NSString *)recipientId timestamp:(uint64_t)timestamp {
if (recipientId.length < 1) {
return;
}
@ -203,23 +184,19 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
}
dispatch_async(self.serialQueue, ^{
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSSet<NSNumber *> *_Nullable oldTimestamps = [transaction objectForKey:recipientId inCollection:collection];
NSSet<NSNumber *> *_Nullable oldTimestamps = [transaction objectForKey:recipientId inCollection:kOutgoingReadReceiptManagerCollection];
NSMutableSet<NSNumber *> *newTimestamps
= (oldTimestamps ? [oldTimestamps mutableCopy] : [NSMutableSet new]);
[newTimestamps addObject:@(timestamp)];
[transaction setObject:newTimestamps forKey:recipientId inCollection:collection];
[transaction setObject:newTimestamps forKey:recipientId inCollection:kOutgoingReadReceiptManagerCollection];
}];
[self process];
});
}
- (void)dequeueReceiptsWithRecipientId:(NSString *)recipientId
timestamps:(NSSet<NSNumber *> *)timestamps
receiptType:(OWSReceiptType)receiptType {
NSString *collection = [self collectionForReceiptType:receiptType];
- (void)dequeueReceiptsWithRecipientId:(NSString *)recipientId timestamps:(NSSet<NSNumber *> *)timestamps {
if (recipientId.length < 1) {
return;
}
@ -228,15 +205,15 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
}
dispatch_async(self.serialQueue, ^{
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSSet<NSNumber *> *_Nullable oldTimestamps = [transaction objectForKey:recipientId inCollection:collection];
NSSet<NSNumber *> *_Nullable oldTimestamps = [transaction objectForKey:recipientId inCollection:kOutgoingReadReceiptManagerCollection];
NSMutableSet<NSNumber *> *newTimestamps
= (oldTimestamps ? [oldTimestamps mutableCopy] : [NSMutableSet new]);
[newTimestamps minusSet:timestamps];
if (newTimestamps.count > 0) {
[transaction setObject:newTimestamps forKey:recipientId inCollection:collection];
[transaction setObject:newTimestamps forKey:recipientId inCollection:kOutgoingReadReceiptManagerCollection];
} else {
[transaction removeObjectForKey:recipientId inCollection:collection];
[transaction removeObjectForKey:recipientId inCollection:kOutgoingReadReceiptManagerCollection];
}
}];
});
@ -247,15 +224,6 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
[self process];
}
- (NSString *)collectionForReceiptType:(OWSReceiptType)receiptType {
switch (receiptType) {
case OWSReceiptType_Delivery:
return kOutgoingDeliveryReceiptManagerCollection;
case OWSReceiptType_Read:
return kOutgoingReadReceiptManagerCollection;
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,5 +1,6 @@
import PromiseKit
import Sodium
import SessionSnodeKit
public protocol SessionMessagingKitStorageProtocol {
@ -52,7 +53,6 @@ public protocol SessionMessagingKitStorageProtocol {
func getAllV2OpenGroups() -> [String:OpenGroupV2]
func getV2OpenGroup(for threadID: String) -> OpenGroupV2?
func v2GetThreadID(for v2OpenGroupID: String) -> String?
func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set<String>, using transaction: Any)
// MARK: - Open Group Public Keys
@ -97,3 +97,5 @@ public protocol SessionMessagingKitStorageProtocol {
/// Also touches the associated message.
func persist(_ stream: TSAttachmentStream, associatedWith tsIncomingMessageID: String, using transaction: Any)
}
extension Storage: SessionMessagingKitStorageProtocol, SessionSnodeKitStorageProtocol {}

View File

@ -140,9 +140,6 @@ typedef NS_ENUM(NSUInteger, OWSRegistrationState) {
- (BOOL)isDeregistered;
- (void)setIsDeregistered:(BOOL)isDeregistered;
- (BOOL)hasPendingBackupRestoreDecision;
- (void)setHasPendingBackupRestoreDecision:(BOOL)value;
#pragma mark - Re-registration
// Re-registration is the process of re-registering _with the same phone number_.

View File

@ -418,22 +418,6 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa
inCollection:TSAccountManager_UserAccountCollection];
}
- (BOOL)hasPendingBackupRestoreDecision
{
return [self.dbConnection boolForKey:TSAccountManager_HasPendingRestoreDecisionKey
inCollection:TSAccountManager_UserAccountCollection
defaultValue:NO];
}
- (void)setHasPendingBackupRestoreDecision:(BOOL)value
{
[self.dbConnection setBool:value
forKey:TSAccountManager_HasPendingRestoreDecisionKey
inCollection:TSAccountManager_UserAccountCollection];
[self postRegistrationStateDidChangeNotification];
}
- (BOOL)isManualMessageFetchEnabled
{
return [self.dbConnection boolForKey:TSAccountManager_ManualMessageFetchKey

View File

@ -15,8 +15,6 @@ extern NSString *const LKED25519SecretKey;
extern NSString *const LKED25519PublicKey;
extern NSString *const OWSPrimaryStorageIdentityKeyStoreCollection;
extern NSString *const OWSPrimaryStorageTrustedKeysCollection;
// This notification will be fired whenever identities are created
// or their verification state changes.
extern NSString *const kNSNotificationName_IdentityStateDidChange;
@ -55,20 +53,8 @@ extern const NSUInteger kStoredIdentityKeyLength;
*/
- (nullable OWSRecipientIdentity *)untrustedIdentityForSendingToRecipientId:(NSString *)recipientId;
- (BOOL)saveRemoteIdentity:(NSData *)identityKey recipientId:(NSString *)recipientId;
- (nullable ECKeyPair *)identityKeyPair;
#pragma mark - Debug
#if DEBUG
// Clears everything except the local identity key.
- (void)clearIdentityState:(YapDatabaseReadWriteTransaction *)transaction;
- (void)snapshotIdentityState:(YapDatabaseReadWriteTransaction *)transaction;
- (void)restoreIdentityState:(YapDatabaseReadWriteTransaction *)transaction;
#endif
@end
NS_ASSUME_NONNULL_END

View File

@ -33,12 +33,6 @@ NSString *const LKED25519SecretKey = @"LKED25519SecretKey";
NSString *const LKED25519PublicKey = @"LKED25519PublicKey";
NSString *const OWSPrimaryStorageIdentityKeyStoreCollection = @"TSStorageManagerIdentityKeyStoreCollection";
// Storing recipients identity keys
NSString *const OWSPrimaryStorageTrustedKeysCollection = @"TSStorageManagerTrustedKeysCollection";
NSString *const OWSIdentityManager_QueuedVerificationStateSyncMessages =
@"OWSIdentityManager_QueuedVerificationStateSyncMessages";
// Don't trust an identity for sending to unless they've been around for at least this long
const NSTimeInterval kIdentityKeyStoreNonBlockingSecondsThreshold = 5.0;
@ -79,8 +73,6 @@ NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationNa
_dbConnection = primaryStorage.newDatabaseConnection;
self.dbConnection.objectCacheEnabled = NO;
[self observeNotifications];
return self;
}
@ -91,14 +83,6 @@ NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationNa
#pragma mark -
- (void)observeNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
- (void)generateNewIdentityKeyPair
{
ECKeyPair *keyPair = [Curve25519 generateKeyPair];
@ -165,70 +149,6 @@ NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationNa
return (int)[TSAccountManager getOrGenerateRegistrationId:transaction];
}
- (BOOL)saveRemoteIdentity:(NSData *)identityKey recipientId:(NSString *)recipientId
{
__block BOOL result;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
result = [self saveRemoteIdentity:identityKey recipientId:recipientId protocolContext:transaction];
}];
return result;
}
- (BOOL)saveRemoteIdentity:(NSData *)identityKey
recipientId:(NSString *)recipientId
protocolContext:(nullable id)protocolContext
{
YapDatabaseReadWriteTransaction *transaction = protocolContext;
// Deprecated. We actually no longer use the OWSPrimaryStorageTrustedKeysCollection for trust
// decisions, but it's desirable to try to keep it up to date with our trusted identitys
// while we're switching between versions, e.g. so we don't get into a state where we have a
// session for an identity not in our key store.
[transaction setObject:identityKey forKey:recipientId inCollection:OWSPrimaryStorageTrustedKeysCollection];
OWSRecipientIdentity *existingIdentity =
[OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction];
if (existingIdentity == nil) {
[[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId
identityKey:identityKey
isFirstKnownKey:YES
createdAt:[NSDate new]
verificationState:OWSVerificationStateDefault]
saveWithTransaction:transaction];
[self fireIdentityStateChangeNotification];
return NO;
}
if (![existingIdentity.identityKey isEqual:identityKey]) {
OWSVerificationState verificationState;
switch (existingIdentity.verificationState) {
case OWSVerificationStateDefault:
verificationState = OWSVerificationStateDefault;
break;
case OWSVerificationStateVerified:
case OWSVerificationStateNoLongerVerified:
verificationState = OWSVerificationStateNoLongerVerified;
break;
}
[[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId
identityKey:identityKey
isFirstKnownKey:NO
createdAt:[NSDate new]
verificationState:verificationState] saveWithTransaction:transaction];
[self fireIdentityStateChangeNotification];
return YES;
}
return NO;
}
- (nullable OWSRecipientIdentity *)recipientIdentityForRecipientId:(NSString *)recipientId
{
__block OWSRecipientIdentity *_Nullable result;
@ -341,65 +261,6 @@ NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationNa
}
}
#pragma mark - Debug
#if DEBUG
- (void)clearIdentityState:(YapDatabaseReadWriteTransaction *)transaction
{
NSMutableArray<NSString *> *identityKeysToRemove = [NSMutableArray new];
[transaction enumerateKeysInCollection:OWSPrimaryStorageIdentityKeyStoreCollection
usingBlock:^(NSString *_Nonnull key, BOOL *_Nonnull stop) {
if ([key isEqualToString:OWSPrimaryStorageIdentityKeyStoreIdentityKey]) {
// Don't delete our own key.
return;
}
[identityKeysToRemove addObject:key];
}];
for (NSString *key in identityKeysToRemove) {
[transaction removeObjectForKey:key inCollection:OWSPrimaryStorageIdentityKeyStoreCollection];
}
[transaction removeAllObjectsInCollection:OWSPrimaryStorageTrustedKeysCollection];
}
- (NSString *)identityKeySnapshotFilePath
{
// Prefix name with period "." so that backups will ignore these snapshots.
NSString *dirPath = [OWSFileSystem appDocumentDirectoryPath];
return [dirPath stringByAppendingPathComponent:@".identity-key-snapshot"];
}
- (NSString *)trustedKeySnapshotFilePath
{
// Prefix name with period "." so that backups will ignore these snapshots.
NSString *dirPath = [OWSFileSystem appDocumentDirectoryPath];
return [dirPath stringByAppendingPathComponent:@".trusted-key-snapshot"];
}
- (void)snapshotIdentityState:(YapDatabaseReadWriteTransaction *)transaction
{
[transaction snapshotCollection:OWSPrimaryStorageIdentityKeyStoreCollection
snapshotFilePath:self.identityKeySnapshotFilePath];
[transaction snapshotCollection:OWSPrimaryStorageTrustedKeysCollection
snapshotFilePath:self.trustedKeySnapshotFilePath];
}
- (void)restoreIdentityState:(YapDatabaseReadWriteTransaction *)transaction
{
[transaction restoreSnapshotOfCollection:OWSPrimaryStorageIdentityKeyStoreCollection
snapshotFilePath:self.identityKeySnapshotFilePath];
[transaction restoreSnapshotOfCollection:OWSPrimaryStorageTrustedKeysCollection
snapshotFilePath:self.trustedKeySnapshotFilePath];
}
#endif
#pragma mark - Notifications
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
}
@end
NS_ASSUME_NONNULL_END

View File

@ -126,7 +126,6 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
// We don't need to use OWSMessageReceiver in the SAE.
// We don't need to use OWSBatchMessageProcessor in the SAE.
// We don't need to use OWSOrphanDataCleaner in the SAE.
// We don't need to fetch the local profile in the SAE
OWSReadReceiptManager.shared().prepareCachedValues()

View File

@ -1,37 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
public class AppPreferences: NSObject {
// Never instantiate this class.
private override init() {}
private static let collection = "AppPreferences"
// MARK: -
private static let hasDimissedFirstConversationCueKey = "hasDimissedFirstConversationCue"
@objc
public static var hasDimissedFirstConversationCue: Bool {
get {
return getBool(key: hasDimissedFirstConversationCueKey)
}
set {
setBool(newValue, key: hasDimissedFirstConversationCueKey)
}
}
// MARK: -
private class func getBool(key: String, defaultValue: Bool = false) -> Bool {
return OWSPrimaryStorage.dbReadConnection().bool(forKey: key, inCollection: collection, defaultValue: defaultValue)
}
private class func setBool(_ value: Bool, key: String) {
OWSPrimaryStorage.dbReadWriteConnection().setBool(value, forKey: key, inCollection: collection)
}
}

View File

@ -1,8 +0,0 @@
extension Storage : SessionMessagingKitStorageProtocol, SessionSnodeKitStorageProtocol {
public func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set<String>, using transaction: Any) {
let transaction = transaction as! YapDatabaseReadWriteTransaction
OWSPrimaryStorage.shared().updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, in: transaction)
}
}

View File

@ -1,11 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#ifndef Signal_TSStorageHeaders_h
#define Signal_TSStorageHeaders_h
#import <SessionMessagingKit/OWSIdentityManager.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+keyFromIntLong.h>
#import <SessionMessagingKit/OWSPrimaryStorage.h>
#endif

View File

@ -1,32 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#ifndef TextSecureKit_TSStorageKeys_h
#define TextSecureKit_TSStorageKeys_h
/**
* Preferences exposed to the user
*/
#pragma mark User Preferences
#define TSStorageUserPreferencesCollection @"TSStorageUserPreferencesCollection"
/**
* Internal settings of the application, not exposed to the user.
*/
#pragma mark Internal Settings
#define TSStorageInternalSettingsCollection @"TSStorageInternalSettingsCollection"
#define TSStorageInternalSettingsVersion @"TSLastLaunchedVersion"
#endif
NS_ASSUME_NONNULL_END

View File

@ -3,6 +3,7 @@
//
import UIKit
import SessionUIKit
protocol AttachmentCaptionDelegate: class {
func captionView(_ captionView: AttachmentCaptionViewController, didChangeCaptionText captionText: String?, attachmentItem: SignalAttachmentItem)
@ -217,8 +218,8 @@ class AttachmentCaptionViewController: OWSViewController {
placeholderTextView.backgroundColor = .clear
placeholderTextView.font = UIFont.ows_dynamicTypeBody
placeholderTextView.textColor = Theme.darkThemePrimaryColor
placeholderTextView.tintColor = Theme.darkThemePrimaryColor
placeholderTextView.textColor = Colors.text
placeholderTextView.tintColor = Colors.text
placeholderTextView.returnKeyType = .done
return placeholderTextView

View File

@ -3,6 +3,7 @@
//
import Foundation
import SessionUIKit
@objc
public protocol MessageApprovalViewControllerDelegate: class {
@ -70,7 +71,7 @@ public class MessageApprovalViewController: OWSViewController, UITextViewDelegat
public override func loadView() {
self.view = UIView.container()
self.view.backgroundColor = Theme.backgroundColor
self.view.backgroundColor = Colors.navigationBarBackground
// Recipient Row
let recipientRow = createRecipientRow()
@ -82,8 +83,8 @@ public class MessageApprovalViewController: OWSViewController, UITextViewDelegat
// Text View
textView = OWSTextView()
textView.delegate = self
textView.backgroundColor = Theme.backgroundColor
textView.textColor = Theme.primaryColor
textView.backgroundColor = Colors.navigationBarBackground
textView.textColor = Colors.text
textView.font = UIFont.ows_dynamicTypeBody
textView.text = self.initialMessageText
textView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
@ -97,11 +98,11 @@ public class MessageApprovalViewController: OWSViewController, UITextViewDelegat
private func createRecipientRow() -> UIView {
let recipientRow = UIView.container()
recipientRow.backgroundColor = Theme.toolbarBackgroundColor
recipientRow.backgroundColor = UIColor.lokiDarkestGray()
// Hairline borders should be 1 pixel, not 1 point.
let borderThickness = 1.0 / UIScreen.main.scale
let borderColor = Theme.middleGrayColor
let borderColor = UIColor(white: 0.5, alpha: 1)
let topBorder = UIView.container()
topBorder.backgroundColor = borderColor
@ -126,12 +127,12 @@ public class MessageApprovalViewController: OWSViewController, UITextViewDelegat
let toLabel = UILabel()
toLabel.text = NSLocalizedString("MESSAGE_APPROVAL_RECIPIENT_LABEL",
comment: "Label for the recipient name in the 'message approval' dialog.")
toLabel.textColor = Theme.secondaryColor
toLabel.textColor = Colors.separator
toLabel.font = font
recipientRow.addSubview(toLabel)
let nameLabel = UILabel()
nameLabel.textColor = Theme.primaryColor
nameLabel.textColor = Colors.text
nameLabel.font = font
nameLabel.lineBreakMode = .byTruncatingTail
recipientRow.addSubview(nameLabel)
@ -164,12 +165,12 @@ public class MessageApprovalViewController: OWSViewController, UITextViewDelegat
let publicKey = contactThread.contactSessionID()
nameLabel.text = Storage.shared.getContact(with: publicKey)?.displayName(for: .regular) ?? publicKey
nameLabel.textColor = Theme.primaryColor
nameLabel.textColor = Colors.text
if let profileName = self.profileName(contactThread: contactThread) {
// If there's a profile name worth showing, add it as a second line below the name.
let profileNameLabel = UILabel()
profileNameLabel.textColor = Theme.secondaryColor
profileNameLabel.textColor = Colors.separator
profileNameLabel.font = font
profileNameLabel.text = profileName
profileNameLabel.lineBreakMode = .byTruncatingTail

View File

@ -30,11 +30,9 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
#import <SignalUtilitiesKit/OWSNavigationController.h>
#import <SignalUtilitiesKit/OWSOperation.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+keyFromIntLong.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+Loki.h>
#import <SignalUtilitiesKit/OWSProfileManager.h>
#import <SignalUtilitiesKit/OWSQueues.h>
#import <SignalUtilitiesKit/OWSResaveCollectionDBMigration.h>
#import <SignalUtilitiesKit/OWSSearchBar.h>
#import <SignalUtilitiesKit/OWSTableViewController.h>
#import <SignalUtilitiesKit/OWSTextField.h>
#import <SignalUtilitiesKit/OWSTextView.h>
@ -43,12 +41,9 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
#import <SignalUtilitiesKit/ScreenLockViewController.h>
#import <SignalUtilitiesKit/SignalAccount.h>
#import <SignalUtilitiesKit/SSKAsserts.h>
#import <SignalUtilitiesKit/Theme.h>
#import <SignalUtilitiesKit/ThreadUtil.h>
#import <SignalUtilitiesKit/ThreadViewHelper.h>
#import <SignalUtilitiesKit/TSConstants.h>
#import <SignalUtilitiesKit/TSStorageHeaders.h>
#import <SignalUtilitiesKit/TSStorageKeys.h>
#import <SignalUtilitiesKit/UIFont+OWS.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/UIViewController+OWS.h>

View File

@ -55,15 +55,6 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
- (void)themeDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
self.navigationBar.barTintColor = [UINavigationBar appearance].barTintColor;
self.navigationBar.tintColor = [UINavigationBar appearance].tintColor;
self.navigationBar.titleTextAttributes = [UINavigationBar appearance].titleTextAttributes;
}
- (void)viewDidLoad
{
[super viewDidLoad];
@ -90,11 +81,6 @@ NS_ASSUME_NONNULL_BEGIN
OWSNavigationBar *navbar = (OWSNavigationBar *)self.navigationBar;
navbar.navBarLayoutDelegate = self;
[self updateLayoutForNavbar:navbar];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(themeDidChange:)
name:ThemeDidChangeNotification
object:nil];
}
// All OWSNavigationController serve as the UINavigationBarDelegate for their navbar.

View File

@ -4,7 +4,6 @@
#import "OWSTableViewController.h"
#import "OWSNavigationController.h"
#import "Theme.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
@ -594,11 +593,6 @@ NSString *const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier";
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kOWSTableCellIdentifier];
[self applyTheme];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(themeDidChange:)
name:ThemeDidChangeNotification
object:nil];
}
- (void)dealloc
@ -816,23 +810,13 @@ NSString *const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier";
[self.delegate tableViewWillBeginDragging];
}
#pragma mark - Theme
- (void)themeDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
[self applyTheme];
[self.tableView reloadData];
}
- (void)applyTheme
{
OWSAssertIsOnMainThread();
self.view.backgroundColor = Theme.backgroundColor;
self.tableView.backgroundColor = Theme.backgroundColor;
self.tableView.separatorColor = Theme.cellSeparatorColor;
self.view.backgroundColor = LKColors.navigationBarBackground;
self.tableView.backgroundColor = LKColors.navigationBarBackground;
self.tableView.separatorColor = LKColors.separator;
}
@end

View File

@ -4,7 +4,7 @@
#import "OWSViewController.h"
#import "UIView+OWS.h"
#import <SignalUtilitiesKit/Theme.h>
#import <SessionUIKit/SessionUIKit.h>
#import "AppContext.h"
NS_ASSUME_NONNULL_BEGIN
@ -87,7 +87,7 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void)
[super viewDidLoad];
if (self.shouldUseTheme) {
self.view.backgroundColor = Theme.backgroundColor;
self.view.backgroundColor = [LKColors navigationBarBackground];
}
}

View File

@ -3,6 +3,7 @@
//
import Foundation
import SessionUIKit
@objc(OWSSheetViewControllerDelegate)
public protocol SheetViewControllerDelegate: class {
@ -54,8 +55,8 @@ public class SheetViewController: UIViewController {
sheetView.setContentHuggingVerticalHigh()
sheetView.setCompressionResistanceHigh()
self.sheetViewVerticalConstraint = sheetView.autoPinEdge(.top, to: .bottom, of: self.view)
handleView.backgroundColor = Theme.isDarkThemeEnabled ? UIColor.ows_white : UIColor.ows_gray05
handleView.backgroundColor = isDarkMode ? UIColor.ows_white : UIColor.ows_gray05
let kHandleViewHeight: CGFloat = 5
handleView.autoSetDimensions(to: CGSize(width: 40, height: kHandleViewHeight))
handleView.layer.cornerRadius = kHandleViewHeight / 2
@ -82,7 +83,7 @@ public class SheetViewController: UIViewController {
let backgroundDuration: TimeInterval = 0.1
UIView.animate(withDuration: backgroundDuration) {
let alpha: CGFloat = Theme.isDarkThemeEnabled ? 0.7 : 0.6
let alpha: CGFloat = isDarkMode ? 0.7 : 0.6
self.view.backgroundColor = UIColor.black.withAlphaComponent(alpha)
}
@ -199,8 +200,7 @@ private class SheetView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = Theme.isDarkThemeEnabled ? UIColor.ows_gray90
: UIColor.ows_gray05
self.backgroundColor = isDarkMode ? UIColor.ows_gray90 : UIColor.ows_gray05
}
required init?(coder aDecoder: NSCoder) {

View File

@ -50,10 +50,6 @@ public class OWSNavigationBar: UINavigationBar {
NotificationCenter.default.addObserver(self, selector: #selector(callDidChange), name: .OWSWindowManagerCallDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didChangeStatusBarFrame), name: UIApplication.didChangeStatusBarFrameNotification, object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(themeDidChange),
name: .ThemeDidChange,
object: nil)
}
// MARK: FirstResponder Stubbing
@ -82,13 +78,13 @@ public class OWSNavigationBar: UINavigationBar {
if UIAccessibility.isReduceTransparencyEnabled {
blurEffectView?.isHidden = true
let color = Theme.navbarBackgroundColor
let color = UIColor.lokiDarkestGray()
let backgroundImage = UIImage(color: color)
self.setBackgroundImage(backgroundImage, for: .default)
} else {
// Make navbar more translucent than default. Navbars remove alpha from any assigned backgroundColor, so
// to achieve transparency, we have to assign a transparent image.
let color = Theme.navbarBackgroundColor
let color = UIColor.lokiDarkestGray()
let backgroundImage = UIImage(color: color)
self.setBackgroundImage(backgroundImage, for: .default)
@ -97,16 +93,10 @@ public class OWSNavigationBar: UINavigationBar {
}
}
@objc
public func themeDidChange() {
Logger.debug("")
applyTheme()
}
@objc
public var respectsTheme: Bool = true {
didSet {
themeDidChange()
applyTheme()
}
}
@ -191,9 +181,9 @@ public class OWSNavigationBar: UINavigationBar {
respectsTheme = false
barStyle = .black
titleTextAttributes = [NSAttributedString.Key.foregroundColor: Theme.darkThemePrimaryColor]
barTintColor = Theme.darkThemeBackgroundColor.withAlphaComponent(0.6)
tintColor = Theme.darkThemePrimaryColor
titleTextAttributes = [NSAttributedString.Key.foregroundColor: Colors.text]
barTintColor = Colors.navigationBarBackground.withAlphaComponent(0.6)
tintColor = Colors.text
switch type {
case .clear:

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface OWSSearchBar : UISearchBar
+ (void)applyThemeToSearchBar:(UISearchBar *)searchBar;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,118 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSSearchBar.h"
#import "Theme.h"
#import "UIView+OWS.h"
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SessionUIKit/SessionUIKit.h>
NS_ASSUME_NONNULL_BEGIN
@implementation OWSSearchBar
- (instancetype)init
{
if (self = [super init]) {
[self ows_configure];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self ows_configure];
}
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
[self ows_configure];
}
return self;
}
- (void)ows_configure
{
[self ows_applyTheme];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(themeDidChange:)
name:ThemeDidChangeNotification
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)ows_applyTheme
{
[self.class applyThemeToSearchBar:self];
}
+ (void)applyThemeToSearchBar:(UISearchBar *)searchBar
{
OWSAssertIsOnMainThread();
UIColor *foregroundColor = UIColor.lokiLightestGray;
searchBar.barTintColor = Theme.backgroundColor;
searchBar.barStyle = Theme.barStyle;
searchBar.tintColor = UIColor.lokiGreen;
// Hide searchBar border.
// Alternatively we could hide the border by using `UISearchBarStyleMinimal`, but that causes an issue when toggling
// from light -> dark -> light theme wherein the textField background color appears darker than it should
// (regardless of our re-setting textfield.backgroundColor below).
searchBar.backgroundImage = [UIImage new];
if (Theme.isDarkThemeEnabled) {
UIImage *clearImage = [UIImage imageNamed:@"searchbar_clear"];
[searchBar setImage:[clearImage asTintedImageWithColor:foregroundColor]
forSearchBarIcon:UISearchBarIconClear
state:UIControlStateNormal];
UIImage *searchImage = [UIImage imageNamed:@"searchbar_search"];
[searchBar setImage:[searchImage asTintedImageWithColor:foregroundColor]
forSearchBarIcon:UISearchBarIconSearch
state:UIControlStateNormal];
} else {
[searchBar setImage:nil forSearchBarIcon:UISearchBarIconClear state:UIControlStateNormal];
[searchBar setImage:nil forSearchBarIcon:UISearchBarIconSearch state:UIControlStateNormal];
}
[searchBar traverseViewHierarchyWithVisitor:^(UIView *view) {
if ([view isKindOfClass:[UITextField class]]) {
UITextField *textField = (UITextField *)view;
textField.backgroundColor = Theme.searchFieldBackgroundColor;
textField.textColor = Theme.primaryColor;
NSString *placeholder = textField.placeholder;
if (placeholder != nil) {
NSMutableAttributedString *attributedPlaceholder = [[NSMutableAttributedString alloc] initWithString:placeholder];
[attributedPlaceholder addAttribute:NSForegroundColorAttributeName value:foregroundColor range:NSMakeRange(0, placeholder.length)];
textField.attributedPlaceholder = attributedPlaceholder;
}
textField.keyboardAppearance = LKAppModeUtilities.isLightMode ? UIKeyboardAppearanceDefault : UIKeyboardAppearanceDark;
}
}];
}
- (void)themeDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
[self ows_applyTheme];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -3,7 +3,6 @@
//
#import "OWSTextField.h"
#import "Theme.h"
#import <SessionUIKit/SessionUIKit.h>

View File

@ -3,7 +3,6 @@
//
#import "OWSTextView.h"
#import "Theme.h"
#import <SessionUIKit/SessionUIKit.h>

View File

@ -3,6 +3,7 @@
//
import Foundation
import SessionUIKit
@objc
public class ToastController: NSObject, ToastViewDelegate {
@ -116,11 +117,11 @@ class ToastView: UIView {
super.init(frame: frame)
self.layer.cornerRadius = 4
self.backgroundColor = Theme.toastBackgroundColor
self.backgroundColor = (isDarkMode ? UIColor.ows_gray75 : UIColor.ows_gray60)
self.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
label.textAlignment = .center
label.textColor = Theme.toastForegroundColor
label.textColor = (isDarkMode ? UIColor.ows_white : UIColor.ows_white)
label.font = UIFont.ows_dynamicTypeBody
label.numberOfLines = 0
self.addSubview(label)

View File

@ -114,10 +114,10 @@ const CGFloat kContactCellAvatarTextMargin = 12;
self.subtitleLabel.font = [UIFont ows_regularFontWithSize:11.f];
self.accessoryLabel.font = [UIFont ows_mediumFontWithSize:13.f];
self.nameLabel.textColor = [Theme primaryColor];
self.profileNameLabel.textColor = [Theme secondaryColor];
self.subtitleLabel.textColor = [Theme secondaryColor];
self.accessoryLabel.textColor = Theme.middleGrayColor;
self.nameLabel.textColor = LKColors.text;
self.profileNameLabel.textColor = LKColors.separator;
self.subtitleLabel.textColor = LKColors.separator;
self.accessoryLabel.textColor = [UIColor colorWithWhite:0.5f alpha:1.f];
}
- (void)configureWithRecipientId:(NSString *)recipientId

View File

@ -1,13 +0,0 @@
#import <SessionMessagingKit/OWSPrimaryStorage.h>
#import <Curve25519Kit/Ed25519.h>
#import <YapDatabase/YapDatabase.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSPrimaryStorage (Loki)
- (void)updateMessageIDCollectionByPruningMessagesWithIDs:(NSSet<NSString *> *)targetMessageIDs in:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(updateMessageIDCollectionByPruningMessagesWithIDs(_:in:));
@end
NS_ASSUME_NONNULL_END

View File

@ -1,26 +0,0 @@
#import "OWSPrimaryStorage+Loki.h"
#import "OWSPrimaryStorage+keyFromIntLong.h"
#import "OWSIdentityManager.h"
#import "NSDate+OWS.h"
#import "TSAccountManager.h"
#import "YapDatabaseConnection+OWS.h"
#import "YapDatabaseTransaction+OWS.h"
#import "NSObject+Casting.h"
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#define LKMessageIDCollection @"LKMessageIDCollection"
@implementation OWSPrimaryStorage (Loki)
- (void)updateMessageIDCollectionByPruningMessagesWithIDs:(NSSet<NSString *> *)targetMessageIDs in:(YapDatabaseReadWriteTransaction *)transaction {
NSMutableArray<NSString *> *serverIDs = [NSMutableArray new];
[transaction enumerateRowsInCollection:LKMessageIDCollection usingBlock:^(NSString *key, id object, id metadata, BOOL *stop) {
if (![object isKindOfClass:NSString.class]) { return; }
NSString *messageID = (NSString *)object;
if (![targetMessageIDs containsObject:messageID]) { return; }
[serverIDs addObject:key];
}];
[transaction removeObjectsForKeys:serverIDs inCollection:LKMessageIDCollection];
}
@end

View File

@ -6,9 +6,6 @@
NS_ASSUME_NONNULL_BEGIN
extern NSString *const kNSNotificationName_ProfileWhitelistDidChange;
extern NSString *const kNSNotificationName_ProfileKeyDidChange;
extern const NSUInteger kOWSProfileManager_NameDataLength;
extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter;

View File

@ -15,7 +15,6 @@
#import <SessionUtilitiesKit/NSNotificationCenter+OWS.h>
#import <SessionUtilitiesKit/NSString+SSK.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+Loki.h>
#import <SessionMessagingKit/SSKEnvironment.h>
#import <SessionMessagingKit/TSAccountManager.h>
#import <SessionMessagingKit/TSGroupThread.h>
@ -26,13 +25,6 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const kNSNotificationName_ProfileWhitelistDidChange = @"kNSNotificationName_ProfileWhitelistDidChange";
NSString *const kOWSProfileManager_UserWhitelistCollection = @"kOWSProfileManager_UserWhitelistCollection";
NSString *const kOWSProfileManager_GroupWhitelistCollection = @"kOWSProfileManager_GroupWhitelistCollection";
NSString *const kNSNotificationName_ProfileKeyDidChange = @"kNSNotificationName_ProfileKeyDidChange";
// The max bytes for a user's profile name, encoded in UTF8.
// Before encrypting and submitting we NULL pad the name data to this length.
const NSUInteger kOWSProfileManager_NameDataLength = 26;

View File

@ -1,68 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
extern NSString *const ThemeDidChangeNotification;
@interface Theme : NSObject
- (instancetype)init NS_UNAVAILABLE;
@property (class, readonly, atomic) BOOL isDarkThemeEnabled;
+ (void)setIsDarkThemeEnabled:(BOOL)value;
@property (class, readonly, nonatomic) UIColor *backgroundColor;
@property (class, readonly, nonatomic) UIColor *primaryColor;
@property (class, readonly, nonatomic) UIColor *secondaryColor;
@property (class, readonly, nonatomic) UIColor *boldColor;
@property (class, readonly, nonatomic) UIColor *offBackgroundColor;
@property (class, readonly, nonatomic) UIColor *middleGrayColor;
@property (class, readonly, nonatomic) UIColor *placeholderColor;
@property (class, readonly, nonatomic) UIColor *hairlineColor;
#pragma mark - Global App Colors
@property (class, readonly, nonatomic) UIColor *navbarBackgroundColor;
@property (class, readonly, nonatomic) UIColor *navbarIconColor;
@property (class, readonly, nonatomic) UIColor *navbarTitleColor;
@property (class, readonly, nonatomic) UIColor *toolbarBackgroundColor;
@property (class, readonly, nonatomic) UIColor *conversationButtonBackgroundColor;
@property (class, readonly, nonatomic) UIColor *cellSelectedColor;
@property (class, readonly, nonatomic) UIColor *cellSeparatorColor;
// In some contexts, e.g. media viewing/sending, we always use "dark theme" UI regardless of the
// users chosen theme.
@property (class, readonly, nonatomic) UIColor *darkThemeNavbarIconColor;
@property (class, readonly, nonatomic) UIColor *darkThemeNavbarBackgroundColor;
@property (class, readonly, nonatomic) UIColor *darkThemeBackgroundColor;
@property (class, readonly, nonatomic) UIColor *darkThemePrimaryColor;
@property (class, readonly, nonatomic) UIBlurEffect *darkThemeBarBlurEffect;
@property (class, readonly, nonatomic) UIColor *galleryHighlightColor;
#pragma mark -
@property (class, readonly, nonatomic) UIBarStyle barStyle;
@property (class, readonly, nonatomic) UIColor *searchFieldBackgroundColor;
@property (class, readonly, nonatomic) UIBlurEffect *barBlurEffect;
@property (class, readonly, nonatomic) UIKeyboardAppearance keyboardAppearance;
@property (class, readonly, nonatomic) UIKeyboardAppearance darkThemeKeyboardAppearance;
#pragma mark -
@property (class, readonly, nonatomic) UIColor *toastForegroundColor;
@property (class, readonly, nonatomic) UIColor *toastBackgroundColor;
@property (class, readonly, nonatomic) UIColor *scrollButtonBackgroundColor;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,217 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "Theme.h"
#import "UIColor+OWS.h"
#import "UIUtil.h"
#import <SessionUtilitiesKit/NSNotificationCenter+OWS.h>
#import <SessionMessagingKit/OWSPrimaryStorage.h>
#import <SessionMessagingKit/YapDatabaseConnection+OWS.h>
#import <SessionUIKit/SessionUIKit.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const ThemeDidChangeNotification = @"ThemeDidChangeNotification";
NSString *const ThemeCollection = @"ThemeCollection";
NSString *const ThemeKeyThemeEnabled = @"ThemeKeyThemeEnabled";
@interface Theme ()
@property (nonatomic) NSNumber *isDarkThemeEnabledNumber;
@end
@implementation Theme
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
static Theme *instance;
dispatch_once(&onceToken, ^{
instance = [Theme new];
});
return instance;
}
+ (BOOL)isDarkThemeEnabled
{
return LKAppModeUtilities.isDarkMode;
}
- (BOOL)isDarkThemeEnabled
{
OWSAssertIsOnMainThread();
return LKAppModeUtilities.isDarkMode;
}
+ (void)setIsDarkThemeEnabled:(BOOL)value
{
return [self.sharedInstance setIsDarkThemeEnabled:value];
}
- (void)setIsDarkThemeEnabled:(BOOL)value
{
return;
}
+ (UIColor *)backgroundColor
{
return LKColors.navigationBarBackground;
}
+ (UIColor *)offBackgroundColor
{
return LKColors.unimportant;
}
+ (UIColor *)primaryColor
{
return LKColors.text;
}
+ (UIColor *)secondaryColor
{
return LKColors.separator;
}
+ (UIColor *)boldColor
{
return (Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor : UIColor.blackColor);
}
+ (UIColor *)middleGrayColor
{
return [UIColor colorWithWhite:0.5f alpha:1.f];
}
+ (UIColor *)placeholderColor
{
return LKColors.navigationBarBackground;
}
+ (UIColor *)hairlineColor
{
return LKColors.separator;
}
#pragma mark - Global App Colors
+ (UIColor *)navbarBackgroundColor
{
return UIColor.lokiDarkestGray;
}
+ (UIColor *)darkThemeNavbarBackgroundColor
{
return UIColor.ows_blackColor;
}
+ (UIColor *)navbarIconColor
{
return UIColor.lokiGreen;
}
+ (UIColor *)darkThemeNavbarIconColor;
{
return LKColors.text;
}
+ (UIColor *)navbarTitleColor
{
return Theme.primaryColor;
}
+ (UIColor *)toolbarBackgroundColor
{
return self.navbarBackgroundColor;
}
+ (UIColor *)cellSelectedColor
{
return UIColor.lokiDarkGray;
}
+ (UIColor *)cellSeparatorColor
{
return Theme.hairlineColor;
}
+ (UIColor *)darkThemeBackgroundColor
{
return LKColors.navigationBarBackground;
}
+ (UIColor *)darkThemePrimaryColor
{
return LKColors.text;
}
+ (UIColor *)galleryHighlightColor
{
return UIColor.lokiGreen;
}
+ (UIColor *)conversationButtonBackgroundColor
{
return (Theme.isDarkThemeEnabled ? [UIColor colorWithWhite:0.35f alpha:1.f] : UIColor.ows_gray02Color);
}
+ (UIBlurEffect *)barBlurEffect
{
return Theme.isDarkThemeEnabled ? self.darkThemeBarBlurEffect
: [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
}
+ (UIBlurEffect *)darkThemeBarBlurEffect
{
return [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
}
+ (UIKeyboardAppearance)keyboardAppearance
{
return LKAppModeUtilities.isLightMode ? UIKeyboardAppearanceDefault : UIKeyboardAppearanceDark;
}
+ (UIKeyboardAppearance)darkThemeKeyboardAppearance;
{
return UIKeyboardAppearanceDark;
}
#pragma mark - Search Bar
+ (UIBarStyle)barStyle
{
return Theme.isDarkThemeEnabled ? UIBarStyleBlack : UIBarStyleDefault;
}
+ (UIColor *)searchFieldBackgroundColor
{
return Theme.isDarkThemeEnabled ? Theme.offBackgroundColor : UIColor.ows_gray05Color;
}
#pragma mark -
+ (UIColor *)toastForegroundColor
{
return (Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor : UIColor.ows_whiteColor);
}
+ (UIColor *)toastBackgroundColor
{
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray75Color : UIColor.ows_gray60Color);
}
+ (UIColor *)scrollButtonBackgroundColor
{
return UIColor.lokiDarkerGray;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -3,7 +3,6 @@
//
#import "UIUtil.h"
#import "Theme.h"
#import "UIColor+OWS.h"
#import <SessionUtilitiesKit/AppContext.h>

View File

@ -4,7 +4,6 @@
#import <UIKit/UIKit.h>
#import "Theme.h"
#import "UIColor+OWS.h"
#import "UIUtil.h"
#import "UIView+OWS.h"
@ -96,7 +95,7 @@ NS_ASSUME_NONNULL_BEGIN
imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
OWSAssertDebug(backImage);
[backButton setImage:backImage forState:UIControlStateNormal];
backButton.tintColor = Theme.navbarIconColor;
backButton.tintColor = UIColor.lokiGreen;
backButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;

View File

@ -7,7 +7,6 @@
#import <SessionUtilitiesKit/AppContext.h>
#import <SignalUtilitiesKit/AppVersion.h>
#import <SessionUtilitiesKit/NSUserDefaults+OWS.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+Loki.h>
#import <SessionMessagingKit/TSAccountManager.h>
#import <SessionMessagingKit/TSThread.h>
#import <SessionMessagingKit/TSGroupThread.h>