Added a simple migration progress indicator and animation (need timing tweaks)

Cleaned up the creation of the GRDBStorage instance
Fixed an issue where the launch screen wasn't setting it's background colour based on the system setting
Renamed the GRDBStorageError to StorageError (in preparation of legacy 'Storage' relocation)
Consolidated the two Environment classes (in Swift)
Refactored the AppSetup class to Swift
This commit is contained in:
Morgan Pretty 2022-05-30 13:04:26 +10:00
parent e2ee0e94ee
commit 26c7a5022a
45 changed files with 567 additions and 704 deletions

View File

@ -184,8 +184,6 @@
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF281255B6D84007E1867 /* OWSAudioSession.swift */; };
B8856D1A256F114D001CE70E /* ProximityMonitoringManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */; };
B8856D23256F116B001CE70E /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2EF255B6DBB007E1867 /* Weak.swift */; };
B8856D34256F1192001CE70E /* Environment.m in Sources */ = {isa = PBXBuildFile; fileRef = C37F5402255BA9ED002AEA92 /* Environment.m */; };
B8856D3D256F11B2001CE70E /* Environment.h in Headers */ = {isa = PBXBuildFile; fileRef = C37F53E8255BA9BB002AEA92 /* Environment.h */; settings = {ATTRIBUTES = (Public, ); }; };
B8856D60256F129B001CE70E /* OWSAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8856D5F256F129B001CE70E /* OWSAlerts.swift */; };
B8856D69256F141F001CE70E /* OWSWindowManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF306255B6DBE007E1867 /* OWSWindowManager.m */; };
B8856D72256F1421001CE70E /* OWSWindowManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2FB255B6DBD007E1867 /* OWSWindowManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -421,8 +419,6 @@
C38EF275255B6D7A007E1867 /* OWSDatabaseMigrationRunner.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF26F255B6D79007E1867 /* OWSDatabaseMigrationRunner.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38EF276255B6D7A007E1867 /* OWSDatabaseMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF270255B6D79007E1867 /* OWSDatabaseMigration.m */; };
C38EF277255B6D7A007E1867 /* OWSDatabaseMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF271255B6D79007E1867 /* OWSDatabaseMigration.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38EF28F255B6D86007E1867 /* VersionMigrations.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF283255B6D84007E1867 /* VersionMigrations.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38EF292255B6D86007E1867 /* VersionMigrations.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF286255B6D85007E1867 /* VersionMigrations.m */; };
C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A2255B6D93007E1867 /* Identicon+ObjC.swift */; };
C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */; };
C38EF2A7255B6D93007E1867 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; };
@ -680,6 +676,8 @@
FD848B9328420164000E298B /* UnicodeScalar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9228420164000E298B /* UnicodeScalar+Utilities.swift */; };
FD848B9628422A2A000E298B /* MessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B86283B844B000E298B /* MessageViewModel.swift */; };
FD848B9828422F1A000E298B /* Date+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9728422F1A000E298B /* Date+Utilities.swift */; };
FD848B9A28442CE6000E298B /* StorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9928442CE6000E298B /* StorageError.swift */; };
FD848B9C284435D7000E298B /* AppSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9B284435D7000E298B /* AppSetup.swift */; };
FD859F0027C4691300510D0C /* MockDataGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFF27C4691300510D0C /* MockDataGenerator.swift */; };
FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; };
FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; };
@ -690,8 +688,6 @@
FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; };
FDA8EAFE280E8B78002B68E5 /* FailedMessageSendsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */; };
FDA8EB00280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */; };
FDA8EB09280E90FB002B68E5 /* AppSetup.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF287255B6D85007E1867 /* AppSetup.m */; };
FDA8EB0A280E9103002B68E5 /* AppSetup.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF284255B6D84007E1867 /* AppSetup.h */; settings = {ATTRIBUTES = (Public, ); }; };
FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */; };
FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC62838B91E00B7C95D /* LinkPreviewError.swift */; };
FDB4BBC92839BEF000B7C95D /* ProfileManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */; };
@ -710,7 +706,7 @@
FDF0B74B28061F7A004C14C5 /* InteractionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B74A28061F7A004C14C5 /* InteractionAttachment.swift */; };
FDF0B74F28079E5E004C14C5 /* SendReadReceiptsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B74E28079E5E004C14C5 /* SendReadReceiptsJob.swift */; };
FDF0B7512807BA56004C14C5 /* NotificationsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7502807BA56004C14C5 /* NotificationsProtocol.swift */; };
FDF0B7552807C4BB004C14C5 /* SSKEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7542807C4BB004C14C5 /* SSKEnvironment.swift */; };
FDF0B7552807C4BB004C14C5 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7542807C4BB004C14C5 /* Environment.swift */; };
FDF0B7582807F368004C14C5 /* MessageReceiverError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7572807F368004C14C5 /* MessageReceiverError.swift */; };
FDF0B75A2807F3A3004C14C5 /* MessageSenderError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7592807F3A3004C14C5 /* MessageSenderError.swift */; };
FDF0B75C2807F41D004C14C5 /* MessageSender+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B75B2807F41D004C14C5 /* MessageSender+Convenience.swift */; };
@ -1337,8 +1333,6 @@
C374EEEA25DA3CA70073A857 /* ConversationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTitleView.swift; sourceTree = "<group>"; };
C374EEF325DB31D40073A857 /* VoiceMessageRecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingView.swift; sourceTree = "<group>"; };
C379DCF3256735770002D4EB /* VisibleMessage+Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Attachment.swift"; sourceTree = "<group>"; };
C37F53E8255BA9BB002AEA92 /* Environment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Environment.h; sourceTree = "<group>"; };
C37F5402255BA9ED002AEA92 /* Environment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Environment.m; sourceTree = "<group>"; };
C38D5E8C2575011E00B6A65C /* MessageSender+ClosedGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageSender+ClosedGroups.swift"; sourceTree = "<group>"; };
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SNProtoEnvelope+Conversion.swift"; sourceTree = "<group>"; };
C38EF224255B6D5D007E1867 /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SignalAttachment.swift; path = "SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift"; sourceTree = SOURCE_ROOT; };
@ -1364,10 +1358,6 @@
C38EF270255B6D79007E1867 /* OWSDatabaseMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDatabaseMigration.m; path = SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigration.m; sourceTree = SOURCE_ROOT; };
C38EF271255B6D79007E1867 /* OWSDatabaseMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSDatabaseMigration.h; path = SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigration.h; sourceTree = SOURCE_ROOT; };
C38EF281255B6D84007E1867 /* OWSAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSAudioSession.swift; path = SessionMessagingKit/Utilities/OWSAudioSession.swift; sourceTree = SOURCE_ROOT; };
C38EF283255B6D84007E1867 /* VersionMigrations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VersionMigrations.h; path = SignalUtilitiesKit/Utilities/VersionMigrations.h; sourceTree = SOURCE_ROOT; };
C38EF284255B6D84007E1867 /* AppSetup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppSetup.h; path = SignalUtilitiesKit/Utilities/AppSetup.h; sourceTree = SOURCE_ROOT; };
C38EF286255B6D85007E1867 /* VersionMigrations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VersionMigrations.m; path = SignalUtilitiesKit/Utilities/VersionMigrations.m; sourceTree = SOURCE_ROOT; };
C38EF287255B6D85007E1867 /* AppSetup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppSetup.m; path = SignalUtilitiesKit/Utilities/AppSetup.m; sourceTree = SOURCE_ROOT; };
C38EF2A2255B6D93007E1867 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Identicon+ObjC.swift"; path = "SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift"; sourceTree = SOURCE_ROOT; };
C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlaceholderIcon.swift; path = "SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift"; sourceTree = SOURCE_ROOT; };
C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfilePictureView.swift; path = "SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift"; sourceTree = SOURCE_ROOT; };
@ -1648,6 +1638,8 @@
FD848B8E283EF2A8000E298B /* UIScrollView+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Utilities.swift"; sourceTree = "<group>"; };
FD848B9228420164000E298B /* UnicodeScalar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnicodeScalar+Utilities.swift"; sourceTree = "<group>"; };
FD848B9728422F1A000E298B /* Date+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Utilities.swift"; sourceTree = "<group>"; };
FD848B9928442CE6000E298B /* StorageError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageError.swift; sourceTree = "<group>"; };
FD848B9B284435D7000E298B /* AppSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSetup.swift; sourceTree = "<group>"; };
FD859EFF27C4691300510D0C /* MockDataGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDataGenerator.swift; sourceTree = "<group>"; };
FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; };
FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = "<group>"; };
@ -1676,7 +1668,7 @@
FDF0B74A28061F7A004C14C5 /* InteractionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionAttachment.swift; sourceTree = "<group>"; };
FDF0B74E28079E5E004C14C5 /* SendReadReceiptsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendReadReceiptsJob.swift; sourceTree = "<group>"; };
FDF0B7502807BA56004C14C5 /* NotificationsProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsProtocol.swift; sourceTree = "<group>"; };
FDF0B7542807C4BB004C14C5 /* SSKEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKEnvironment.swift; sourceTree = "<group>"; };
FDF0B7542807C4BB004C14C5 /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = "<group>"; };
FDF0B7572807F368004C14C5 /* MessageReceiverError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReceiverError.swift; sourceTree = "<group>"; };
FDF0B7592807F3A3004C14C5 /* MessageSenderError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSenderError.swift; sourceTree = "<group>"; };
FDF0B75B2807F41D004C14C5 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageSender+Convenience.swift"; sourceTree = "<group>"; };
@ -2125,6 +2117,7 @@
FD17D7CB27F546F500122BE0 /* Models */,
FD17D7B427F51E6700122BE0 /* Types */,
FD17D7BB27F51F5C00122BE0 /* Utilities */,
FD848B9928442CE6000E298B /* StorageError.swift */,
FD28A4F527EAD44C00FF65E7 /* GRDBStorage.swift */,
C33FDBAB255A581500E217F9 /* OWSFileSystem.h */,
C33FDA8E255A57FD00E217F9 /* OWSFileSystem.m */,
@ -2873,8 +2866,7 @@
C33FDB75255A581000E217F9 /* AppReadiness.m */,
FD09799027FD499200936362 /* BoxKeyPair+Utilities.swift */,
C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */,
C37F53E8255BA9BB002AEA92 /* Environment.h */,
C37F5402255BA9ED002AEA92 /* Environment.m */,
FDF0B7542807C4BB004C14C5 /* Environment.swift */,
C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */,
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */,
C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */,
@ -2894,7 +2886,6 @@
FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */,
C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */,
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */,
FDF0B7542807C4BB004C14C5 /* SSKEnvironment.swift */,
C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */,
C3ECBF7A257056B700EA7FCE /* Threading.swift */,
C33FDB5F255A580E00E217F9 /* YapDatabaseConnection+OWS.h */,
@ -3032,8 +3023,7 @@
C3CA3B11255CF17200F4C6D4 /* Utilities */ = {
isa = PBXGroup;
children = (
C38EF284255B6D84007E1867 /* AppSetup.h */,
C38EF287255B6D85007E1867 /* AppSetup.m */,
FD848B9B284435D7000E298B /* AppSetup.swift */,
C38EF302255B6DBE007E1867 /* OWSAnyTouchGestureRecognizer.h */,
C38EF2F0255B6DBB007E1867 /* OWSAnyTouchGestureRecognizer.m */,
FDCDB8DD2810F73B00352A0C /* Differentiable+Utilities.swift */,
@ -3071,8 +3061,6 @@
C38EF2F2255B6DBC007E1867 /* Searcher.swift */,
B8856D5F256F129B001CE70E /* OWSAlerts.swift */,
C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */,
C38EF283255B6D84007E1867 /* VersionMigrations.h */,
C38EF286255B6D85007E1867 /* VersionMigrations.m */,
C33FDA8B255A57FD00E217F9 /* AppVersion.m */,
C33FDB69255A580F00E217F9 /* FeatureFlags.swift */,
C33FDA99255A57FE00E217F9 /* OutageDetection.swift */,
@ -3538,7 +3526,6 @@
C33FDD7C255A582000E217F9 /* SSKAsserts.h in Headers */,
C38EF3F6255B6DF7007E1867 /* OWSTextView.h in Headers */,
C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */,
FDA8EB0A280E9103002B68E5 /* AppSetup.h in Headers */,
C38EF32B255B6DBF007E1867 /* OWSFormat.h in Headers */,
C33FDDB8255A582000E217F9 /* NSSet+Functional.h in Headers */,
C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */,
@ -3562,7 +3549,6 @@
C33FD9AF255A548A00E217F9 /* SignalUtilitiesKit.h in Headers */,
C33FDC50255A582000E217F9 /* OWSDispatch.h in Headers */,
C33FDD06255A582000E217F9 /* AppVersion.h in Headers */,
C38EF28F255B6D86007E1867 /* VersionMigrations.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3613,7 +3599,6 @@
C32A026C25A801AF000ED5D4 /* NSData+messagePadding.h in Headers */,
B8856D72256F1421001CE70E /* OWSWindowManager.h in Headers */,
B8856CF7256F105E001CE70E /* OWSAudioPlayer.h in Headers */,
B8856D3D256F11B2001CE70E /* Environment.h in Headers */,
C32C5E7E256DE023003C73A2 /* YapDatabaseTransaction+OWS.h in Headers */,
C32C5EA0256DE0D6003C73A2 /* OWSPrimaryStorage.h in Headers */,
B8856E33256F18D5001CE70E /* OWSStorage+Subclass.h in Headers */,
@ -4306,7 +4291,6 @@
C38EF247255B6D67007E1867 /* NSAttributedString+OWS.m in Sources */,
C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */,
C38EF3C5255B6DE7007E1867 /* OWSViewController+ImageEditor.swift in Sources */,
FDA8EB09280E90FB002B68E5 /* AppSetup.m in Sources */,
C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */,
C38EF273255B6D7A007E1867 /* OWSDatabaseMigrationRunner.m in Sources */,
C38EF31A255B6DBF007E1867 /* OWSAnyTouchGestureRecognizer.m in Sources */,
@ -4384,7 +4368,6 @@
C33FDC78255A582000E217F9 /* TSConstants.m in Sources */,
C38EF324255B6DBF007E1867 /* Bench.swift in Sources */,
FDCDB8DE2810F73B00352A0C /* Differentiable+Utilities.swift in Sources */,
C38EF292255B6D86007E1867 /* VersionMigrations.m in Sources */,
C38EF3F9255B6DF7007E1867 /* OWSLayerView.swift in Sources */,
C33FDD03255A582000E217F9 /* WeakTimer.swift in Sources */,
B8B3204E258C15C80020074B /* ContactsMigration.swift in Sources */,
@ -4400,6 +4383,7 @@
B8C2B2C82563685C00551B4D /* CircleView.swift in Sources */,
C38EF331255B6DBF007E1867 /* UIGestureRecognizer+OWS.swift in Sources */,
C33FDDC5255A582000E217F9 /* OWSError.m in Sources */,
FD848B9C284435D7000E298B /* AppSetup.swift in Sources */,
C38EF38D255B6DD2007E1867 /* AttachmentCaptionViewController.swift in Sources */,
C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */,
C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */,
@ -4501,6 +4485,7 @@
B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */,
FDF2220F281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift in Sources */,
C3471ED42555386B00297E91 /* AESGCM.swift in Sources */,
FD848B9A28442CE6000E298B /* StorageError.swift in Sources */,
FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */,
C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */,
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
@ -4583,12 +4568,11 @@
FD09797F27FCFBFF00936362 /* OWSAES256Key+Utilities.swift in Sources */,
FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */,
FD09798327FD1A1500936362 /* ClosedGroup.swift in Sources */,
B8856D34256F1192001CE70E /* Environment.m in Sources */,
B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */,
FD09798727FD1B7800936362 /* GroupMember.swift in Sources */,
FD09799127FD499200936362 /* BoxKeyPair+Utilities.swift in Sources */,
FDB4BBC92839BEF000B7C95D /* ProfileManagerError.swift in Sources */,
FDF0B7552807C4BB004C14C5 /* SSKEnvironment.swift in Sources */,
FDF0B7552807C4BB004C14C5 /* Environment.swift in Sources */,
B8856ECE256F1E58001CE70E /* OWSPreferences.m in Sources */,
FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */,
FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */,

View File

@ -873,6 +873,7 @@ extension ConversationVC:
let interaction: Interaction = try? Interaction.fetchOne(db, id: cellViewModel.id),
let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId)
else { return }
try MessageSender.send(
db,
interaction: interaction,

View File

@ -15,7 +15,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
var window: UIWindow?
var backgroundSnapshotBlockerWindow: UIWindow?
var appStartupWindow: UIWindow?
var poller: Poller = Poller()
var hasInitialRootViewController: Bool = false
/// This needs to be a lazy variable to ensure it doesn't get initialized before it actually needs to be used
lazy var poller: Poller = {
return Poller()
}()
// MARK: - Lifecycle
@ -26,8 +31,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
AppModeManager.configure(delegate: self)
Cryptography.seedRandom()
AppVersion.sharedInstance() // TODO: ???
AppVersion.sharedInstance()
// Prevent the device from sleeping during database view async registration
// (e.g. long database upgrades).
@ -35,14 +39,38 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// This block will be cleared in storageIsReady.
DeviceSleepManager.sharedInstance.addBlock(blockObject: self)
let mainWindow: UIWindow = UIWindow(frame: UIScreen.main.bounds)
let loadingViewController: LoadingViewController = LoadingViewController()
AppSetup.setupEnvironment(
appSpecificSingletonBlock: {
appSpecificBlock: {
// Create AppEnvironment
AppEnvironment.shared.setup()
},
migrationCompletion: { [weak self] successful, needsConfigSync in
guard let strongSelf = self else { return }
// Note: Intentionally dispatching sync as we want to wait for these to complete before
// continuing
DispatchQueue.main.sync {
OWSScreenLockUI.sharedManager().setup(withRootWindow: mainWindow)
OWSWindowManager.shared().setup(
withRootWindow: mainWindow,
screenBlockingWindow: OWSScreenLockUI.sharedManager().screenBlockingWindow
)
OWSScreenLockUI.sharedManager().startObserving()
}
},
migrationProgressChanged: { progress, minEstimatedTotalTime in
loadingViewController.updateProgress(
progress: progress,
minEstimatedTotalTime: minEstimatedTotalTime
)
},
migrationsCompletion: { [weak self] successful, needsConfigSync in
guard let strongSelf = self else { return }
guard successful else {
return
}
Configuration.performMainSetup()
JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens)
// Trigger any launch-specific jobs and start the JobRunner
@ -56,7 +84,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
DeviceSleepManager.sharedInstance.removeBlock(blockObject: strongSelf)
AppVersion.sharedInstance().mainAppLaunchDidComplete()
Environment.shared.audioSession.setup()
SSKEnvironment.shared.reachabilityManager.setup()
Environment.shared.reachabilityManager.setup()
GRDBStorage.shared.writeAsync { db in
// Disable the SAE until the main app has successfully completed launch process
@ -83,18 +111,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
)
Configuration.performMainSetup()
SNAppearance.switchToSessionAppearance()
// No point continuing if we are running tests
guard !CurrentAppContext().isRunningTests else { return true }
let mainWindow: UIWindow = UIWindow(frame: UIScreen.main.bounds)
self.window = mainWindow
CurrentAppContext().mainWindow = mainWindow
// Show LoadingViewController until the async database view registrations are complete.
mainWindow.rootViewController = LoadingViewController()
mainWindow.rootViewController = loadingViewController
mainWindow.makeKeyAndVisible()
adapt(appMode: AppModeManager.getAppModeOrSystemDefault())
@ -104,14 +130,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// Setting the delegate also seems to prevent us from getting the legacy notification
// notification callbacks upon launch e.g. 'didReceiveLocalNotification'
UNUserNotificationCenter.current().delegate = self
OWSScreenLockUI.sharedManager().setup(withRootWindow: mainWindow)
OWSWindowManager.shared().setup(
withRootWindow: mainWindow,
screenBlockingWindow: OWSScreenLockUI.sharedManager().screenBlockingWindow
)
OWSScreenLockUI.sharedManager().startObserving()
NotificationCenter.default.addObserver(
self,
selector: #selector(registrationStateDidChange),
@ -196,10 +215,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
private func verifyDBKeysAvailableBeforeBackgroundLaunch() {
guard UIApplication.shared.applicationState == .background else { return }
let migrationHasRun: Bool = false
// Ensure both databases are accessible (as long as we are supporting the YDB migration
// we should keep this check)
let databasePasswordAccessible: Bool = (
(migrationHasRun && GRDBStorage.isDatabasePasswordAccessible) || // GRDB password access
GRDBStorage.isDatabasePasswordAccessible && // GRDB password access
OWSStorage.isDatabasePasswordAccessible() // YapDatabase password access
)
@ -250,8 +269,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
private func ensureRootViewController() {
// TODO: Add 'MigrationProcessingViewController' in here as well
guard self.window?.rootViewController is LoadingViewController else { return }
guard AppReadiness.isAppReady() && GRDBStorage.shared.isValid && !hasInitialRootViewController else {
return
}
let navController: UINavigationController = OWSNavigationController(
rootViewController: (Identity.userExists() ?

View File

@ -57,7 +57,7 @@ import SignalUtilitiesKit
@objc
public func setup() {
// Hang certain singletons on SSKEnvironment too.
SSKEnvironment.shared.notificationsManager.mutate {
Environment.shared.notificationsManager.mutate {
$0 = notificationPresenter
}
setupLogFiles()

View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -27,7 +28,7 @@
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" red="0.086274509803921567" green="0.086274509803921567" blue="0.086274509803921567" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" name="session_navigation_bar_background"/>
<constraints>
<constraint firstItem="b92-yg-YYQ" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="ec1-lk-fbn"/>
<constraint firstItem="b92-yg-YYQ" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="jLr-XH-MKf"/>
@ -41,5 +42,8 @@
</scenes>
<resources>
<image name="SessionGreen64" width="57.5" height="64"/>
<namedColor name="session_navigation_bar_background">
<color red="0.9882352941176471" green="0.9882352941176471" blue="0.9882352941176471" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View File

@ -1,115 +1,150 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import UIKit
import PromiseKit
import SessionUIKit
// The initial presentation is intended to be indistinguishable from the Launch Screen.
// After a delay we present some "loading" UI so the user doesn't think the app is frozen.
@objc
public class LoadingViewController: UIViewController {
/// This value specifies the minimum expected duration which needs to be hit before the loading UI needs to be show
private static let minExpectedDurationToShowLoading: TimeInterval = 5
/// This value specifies the minimum expected duration which needs to be hit before the additional "might take a few minutes"
/// label gets shown
private static let minExpectedDurationAdditionalLabel: TimeInterval = 15
private var isShowingProgress: Bool = false
// MARK: - UI
override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
private var logoView: UIImageView = {
let result: UIImageView = UIImageView(image: #imageLiteral(resourceName: "SessionGreen64"))
result.contentMode = .scaleAspectFit
result.layer.shadowColor = Colors.accent.cgColor
result.layer.shadowOffset = .zero
result.layer.shadowRadius = 3
result.layer.shadowOpacity = 0
return result
}()
private var progressBar: UIProgressView = {
let result: UIProgressView = UIProgressView(progressViewStyle: .bar)
result.clipsToBounds = true
result.progress = 0
result.tintColor = Colors.accent
result.trackTintColor = Colors.text.withAlphaComponent(0.1)
result.layer.cornerRadius = 6
var logoView: UIImageView!
var topLabel: UILabel!
var bottomLabel: UILabel!
return result
}()
private var topLabel: UILabel = {
let result: UILabel = UILabel()
result.font = UIFont.systemFont(ofSize: Values.mediumFontSize)
result.text = "DATABASE_VIEW_OVERLAY_TITLE".localized()
result.textColor = Colors.text
result.textAlignment = .center
result.numberOfLines = 0
result.lineBreakMode = .byWordWrapping
return result
}()
private var bottomLabel: UILabel = {
let result: UILabel = UILabel()
result.font = UIFont.systemFont(ofSize: Values.verySmallFontSize)
result.text = "DATABASE_VIEW_OVERLAY_SUBTITLE".localized()
result.textColor = Colors.text
result.textAlignment = .center
result.numberOfLines = 0
result.lineBreakMode = .byWordWrapping
result.isHidden = true
return result
}()
private lazy var labelStack: UIStackView = {
let result: UIStackView = UIStackView()
result.axis = .vertical
result.alignment = .center
result.spacing = 20
result.alpha = 0
return result
}()
// MARK: - Lifecycle
override public func loadView() {
self.view = UIView()
// Loki: Set gradient background
view.backgroundColor = .clear
let gradient = Gradients.defaultBackground
view.setGradient(gradient)
self.view.backgroundColor = Colors.navigationBarBackground
self.logoView = UIImageView(image: #imageLiteral(resourceName: "SessionGreen64"))
view.addSubview(logoView)
self.view.addSubview(self.logoView)
self.view.addSubview(self.labelStack)
self.view.addSubview(self.bottomLabel)
self.labelStack.addArrangedSubview(self.progressBar)
self.labelStack.addArrangedSubview(self.topLabel)
logoView.autoCenterInSuperview()
logoView.autoSetDimension(.width, toSize: 64)
logoView.autoSetDimension(.height, toSize: 64)
logoView.contentMode = .scaleAspectFit
self.topLabel = buildLabel()
topLabel.alpha = 0
topLabel.font = UIFont.ows_dynamicTypeTitle2
topLabel.text = NSLocalizedString("DATABASE_VIEW_OVERLAY_TITLE", comment: "Title shown while the app is updating its database.")
self.bottomLabel = buildLabel()
bottomLabel.alpha = 0
bottomLabel.font = UIFont.ows_dynamicTypeBody
bottomLabel.text = NSLocalizedString("DATABASE_VIEW_OVERLAY_SUBTITLE", comment: "Subtitle shown while the app is updating its database.")
let labelStack = UIStackView(arrangedSubviews: [topLabel, bottomLabel])
labelStack.axis = .vertical
labelStack.alignment = .center
labelStack.spacing = 8
view.addSubview(labelStack)
labelStack.autoPinEdge(.top, to: .bottom, of: logoView, withOffset: 20)
labelStack.autoPinLeadingToSuperviewMargin()
labelStack.autoPinTrailingToSuperviewMargin()
labelStack.setCompressionResistanceHigh()
labelStack.setContentHuggingHigh()
// Layout
self.logoView.autoCenterInSuperview()
self.logoView.autoSetDimension(.width, toSize: 64)
self.logoView.autoSetDimension(.height, toSize: 64)
self.progressBar.set(.height, to: (self.progressBar.layer.cornerRadius * 2))
self.progressBar.set(.width, to: .width, of: self.view, multiplier: 0.5)
self.labelStack.pin(.top, to: .bottom, of: self.logoView, withInset: 40)
self.labelStack.pin(.left, to: .left, of: self.view)
self.labelStack.pin(.right, to: .right, of: self.view)
self.labelStack.setCompressionResistanceHigh()
self.labelStack.setContentHuggingHigh()
self.bottomLabel.pin(.top, to: .bottom, of: self.labelStack, withInset: 10)
self.bottomLabel.pin(.left, to: .left, of: self.view)
self.bottomLabel.pin(.right, to: .right, of: self.view)
self.bottomLabel.setCompressionResistanceHigh()
self.bottomLabel.setContentHuggingHigh()
}
var isShowingTopLabel = false
var isShowingBottomLabel = false
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// We only show the "loading" UI if it's a slow launch. Otherwise this ViewController
// should be indistinguishable from the launch screen.
let kTopLabelThreshold: TimeInterval = 5
DispatchQueue.main.asyncAfter(deadline: .now() + kTopLabelThreshold) { [weak self] in
guard let strongSelf = self else {
return
// MARK: - Functions
public func updateProgress(progress: CGFloat, minEstimatedTotalTime: TimeInterval) {
guard minEstimatedTotalTime >= LoadingViewController.minExpectedDurationToShowLoading else { return }
if !self.isShowingProgress {
self.isShowingProgress = true
self.bottomLabel.isHidden = (
minEstimatedTotalTime < LoadingViewController.minExpectedDurationAdditionalLabel
)
UIView.animate(withDuration: 0.3) { [weak self] in
self?.labelStack.alpha = 1
}
guard !strongSelf.isShowingTopLabel else {
return
}
strongSelf.isShowingTopLabel = true
UIView.animate(withDuration: 0.1) {
strongSelf.topLabel.alpha = 1
}
UIView.animate(withDuration: 0.9, delay: 2, options: [.autoreverse, .repeat, .curveEaseInOut], animations: {
strongSelf.topLabel.alpha = 0.2
}, completion: nil)
UIView.animate(
withDuration: 1.95,
delay: 0.05,
options: [
.curveEaseInOut,
.autoreverse,
.repeat
],
animations: { [weak self] in
self?.logoView.layer.shadowOpacity = 1
},
completion: nil
)
}
let kBottomLabelThreshold: TimeInterval = 15
DispatchQueue.main.asyncAfter(deadline: .now() + kBottomLabelThreshold) { [weak self] in
guard let strongSelf = self else {
return
}
guard !strongSelf.isShowingBottomLabel else {
return
}
strongSelf.isShowingBottomLabel = true
UIView.animate(withDuration: 0.1) {
strongSelf.bottomLabel.alpha = 1
}
}
}
// MARK: Orientation
override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
// MARK:
private func buildLabel() -> UILabel {
let label = UILabel()
label.textColor = .white
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
return label
self.progressBar.setProgress(Float(progress), animated: true)
}
}

View File

@ -348,7 +348,7 @@ NS_ASSUME_NONNULL_BEGIN
return ScreenLockUIStateNone;
}
if (Environment.shared.isRequestingPermission) {
if (SMKEnvironment.shared.isRequestingPermission) {
return ScreenLockUIStateNone;
}

View File

@ -486,7 +486,7 @@ public enum SMKLegacy {
let admins: [Data] = self.admins
else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
return .new(
@ -504,7 +504,7 @@ public enum SMKLegacy {
case "encryptionKeyPair":
guard let wrappers: [_KeyPairWrapper] = self.wrappers else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
return .encryptionKeyPair(
@ -515,7 +515,7 @@ public enum SMKLegacy {
let encryptedKeyPair: Data = wrapper.encryptedKeyPair
else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
return SessionMessagingKit.ClosedGroupControlMessage.KeyPairWrapper(
@ -528,7 +528,7 @@ public enum SMKLegacy {
case "nameChange":
guard let name: String = self.name else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
return .nameChange(
@ -538,7 +538,7 @@ public enum SMKLegacy {
case "membersAdded":
guard let members: [Data] = self.members else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
return .membersAdded(members: members)
@ -546,14 +546,14 @@ public enum SMKLegacy {
case "membersRemoved":
guard let members: [Data] = self.members else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
return .membersRemoved(members: members)
case "memberLeft": return .memberLeft
case "encryptionKeyPairRequest": return .encryptionKeyPairRequest
default: throw GRDBStorageError.migrationFailed
default: throw StorageError.migrationFailed
}
}()
)
@ -592,12 +592,12 @@ public enum SMKLegacy {
case "mediaSaved":
guard let timestamp: UInt64 = self.timestamp else {
SNLog("[Migration Error] Unable to decode Legacy DataExtractionNotification")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
return .mediaSaved(timestamp: timestamp)
default: throw GRDBStorageError.migrationFailed
default: throw StorageError.migrationFailed
}
}()
)

View File

@ -6,6 +6,8 @@ import SessionUtilitiesKit
enum _001_InitialSetupMigration: Migration {
static let identifier: String = "initialSetup"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static func migrate(_ db: Database) throws {
// Define the tokenizer to be used in all the FTS tables

View File

@ -10,6 +10,8 @@ import SessionSnodeKit
/// before running the `YDBToGRDBMigration`
enum _002_SetupStandardJobs: Migration {
static let identifier: String = "SetupStandardJobs"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static func migrate(_ db: Database) throws {
// Start by adding the jobs that don't have collections (in the jobs like these

View File

@ -11,8 +11,12 @@ import SessionSnodeKit
// ~250k messages and ~1000 threads seems to take up
enum _003_YDBToGRDBMigration: Migration {
static let identifier: String = "YDBToGRDBMigration"
static let minExpectedRunDuration: TimeInterval = 20
static let needsConfigSync: Bool = true
static func migrate(_ db: Database) throws {
let targetIdentifier: TargetMigrations.Identifier = .messagingKit
// MARK: - Process Contacts, Threads & Interactions
print("RAWR [\(Date().timeIntervalSince1970)] - SessionMessagingKit migration - Start")
var shouldFailMigration: Bool = false
@ -283,7 +287,7 @@ enum _003_YDBToGRDBMigration: Migration {
}
// We can't properly throw within the 'enumerateKeysAndObjects' block so have to throw here
guard !shouldFailMigration else { throw GRDBStorageError.migrationFailed }
guard !shouldFailMigration else { throw StorageError.migrationFailed }
// Insert the data into GRDB
@ -411,7 +415,7 @@ enum _003_YDBToGRDBMigration: Migration {
try legacyThreads.sorted(by: { lhs, rhs in lhs.uniqueId < rhs.uniqueId }).forEach { legacyThread in
guard let threadId: String = legacyThreadIdToIdMap[legacyThread.uniqueId] else {
SNLog("[Migration Error] Unable to migrate thread with no id mapping")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
let threadVariant: SessionThread.Variant
@ -464,7 +468,7 @@ enum _003_YDBToGRDBMigration: Migration {
let formationTimestamp: UInt64 = closedGroupFormation[legacyThread.uniqueId]
else {
SNLog("[Migration Error] Closed group missing required data")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
try ClosedGroup(
@ -519,7 +523,7 @@ enum _003_YDBToGRDBMigration: Migration {
if legacyThread.isOpenGroup {
guard let openGroup: OpenGroupV2 = openGroupInfo[legacyThread.uniqueId] else {
SNLog("[Migration Error] Open group missing required data")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
try OpenGroup(
@ -673,7 +677,7 @@ enum _003_YDBToGRDBMigration: Migration {
default:
// TODO: What message types have no body?
SNLog("[Migration Error] Unsupported interaction type")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
// Insert the data
@ -727,7 +731,7 @@ enum _003_YDBToGRDBMigration: Migration {
guard let interactionId: Int64 = interaction.id else {
// TODO: Is it possible the old database has duplicates which could hit this case?
SNLog("[Migration Error] Failed to insert interaction")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
// Store the interactionId in the lookup map to simplify job creation later
@ -874,7 +878,7 @@ enum _003_YDBToGRDBMigration: Migration {
guard linkPreview.imageAttachmentId == nil || attachments[linkPreview.imageAttachmentId ?? ""] != nil else {
// TODO: Is it possible to hit this case if a quoted attachment hasn't been downloaded?
SNLog("[Migration Error] Missing link preview attachment")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
// Setup the attachment and add it to the lookup (if it exists)
@ -1268,7 +1272,7 @@ enum _003_YDBToGRDBMigration: Migration {
try attachmentUploadJobs.forEach { legacyJob in
guard let sendJob: Job = messageSendJobLegacyMap[legacyJob.messageSendJobID], let sendJobId: Int64 = sendJob.id else {
SNLog("[Migration Error] attachmentUpload job missing associated MessageSendJob")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
let uploadJob: Job? = try Job(
@ -1286,7 +1290,7 @@ enum _003_YDBToGRDBMigration: Migration {
// Add the dependency to the relevant MessageSendJob
guard let uploadJobId: Int64 = uploadJob?.id else {
SNLog("[Migration Error] attachmentUpload job was not created")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
try JobDependencies(
@ -1302,7 +1306,7 @@ enum _003_YDBToGRDBMigration: Migration {
try attachmentDownloadJobs.forEach { legacyJob in
guard let interactionId: Int64 = legacyInteractionToIdMap[legacyJob.tsMessageID] else {
SNLog("[Migration Error] attachmentDownload job unable to find interaction")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
guard processedAttachmentIds.contains(legacyJob.attachmentID) else {
SNLog("[Migration Error] attachmentDownload job unable to find attachment")
@ -1441,7 +1445,7 @@ enum _003_YDBToGRDBMigration: Migration {
guard !processedAttachmentIds.contains(legacyAttachmentId) else {
guard isQuotedMessage else {
SNLog("[Migration Error] Attempted to process duplicate attachment")
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
return legacyAttachmentId

View File

@ -983,7 +983,7 @@ extension Attachment {
guard uploadedAttachment != nil else {
SNLog("Couldn't update attachmentUpload job.")
failure?(GRDBStorageError.failedToSave)
failure?(StorageError.failedToSave)
return
}
@ -1025,7 +1025,7 @@ extension Attachment {
guard updatedAttachment != nil else {
SNLog("Couldn't update attachmentUpload job.")
failure?(GRDBStorageError.failedToSave)
failure?(StorageError.failedToSave)
return
}
@ -1049,7 +1049,7 @@ extension Attachment {
guard uploadedAttachment != nil else {
SNLog("Couldn't update attachmentUpload job.")
failure?(GRDBStorageError.failedToSave)
failure?(StorageError.failedToSave)
return
}

View File

@ -121,7 +121,7 @@ public extension Profile {
{
guard let validProfileKey: OWSAES256Key = OWSAES256Key(data: profileKeyData) else {
owsFailDebug("Failed to make profile key for key data")
throw GRDBStorageError.decodingFailed
throw StorageError.decodingFailed
}
profileKey = validProfileKey

View File

@ -94,7 +94,7 @@ public extension DisappearingMessagesJob {
do {
guard let interactionId: Int64 = try? (interaction.id ?? interaction.inserted(db).id) else {
throw GRDBStorageError.objectNotFound
throw StorageError.objectNotFound
}
return updateNextRunIfNeeded(db, interactionIds: [interactionId], startedAtMs: startedAtMs)

View File

@ -211,18 +211,18 @@ extension MessageSendJob {
guard let messageType: String = try? container.decode(String.self, forKey: .messageType) else {
Logger.error("Unable to decode messageSend job due to missing messageType")
throw GRDBStorageError.decodingFailed
throw StorageError.decodingFailed
}
/// Note: This **MUST** be a `Codable.Type` rather than a `Message.Type` otherwise the decoding will result
/// in a `Message` object being returned rather than the desired subclass
guard let MessageType: Codable.Type = MessageSendJob.Details.supportedMessageTypes[messageType] else {
Logger.error("Unable to decode messageSend job due to unsupported messageType")
throw GRDBStorageError.decodingFailed
throw StorageError.decodingFailed
}
guard let message: Message = try MessageType.decoded(with: container, forKey: .message) as? Message else {
Logger.error("Unable to decode messageSend job due to message conversion issue")
throw GRDBStorageError.decodingFailed
throw StorageError.decodingFailed
}
self = Details(
@ -241,7 +241,7 @@ extension MessageSendJob {
guard let messageTypeString: String = maybeMessageTypeString else {
Logger.error("Unable to encode messageSend job due to unsupported messageType")
throw GRDBStorageError.objectNotFound
throw StorageError.objectNotFound
}
try container.encode(destination, forKey: .destination)

View File

@ -17,7 +17,7 @@ public extension Message {
case .closedGroup: return .closedGroup(groupPublicKey: thread.id)
case .openGroup:
guard let openGroup: OpenGroup = try thread.openGroup.fetchOne(db) else {
throw GRDBStorageError.objectNotFound
throw StorageError.objectNotFound
}
return .openGroupV2(room: openGroup.room, server: openGroup.server)

View File

@ -494,7 +494,7 @@ extension MessageReceiver {
throw error
}
guard let interactionId: Int64 = interaction.id else { throw GRDBStorageError.failedToSave }
guard let interactionId: Int64 = interaction.id else { throw StorageError.failedToSave }
// Update and recipient and read states as needed
try updateRecipientAndReadStates(

View File

@ -230,9 +230,7 @@ extension MessageSender {
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000))
).inserted(db)
guard let interactionId: Int64 = interaction.id else {
throw GRDBStorageError.objectNotSaved
}
guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
// Send the update to the group
let closedGroupControlMessage = ClosedGroupControlMessage(kind: .nameChange(name: name))
@ -305,10 +303,10 @@ extension MessageSender {
thread: SessionThread
) throws {
guard let disappearingMessagesConfig: DisappearingMessagesConfiguration = try thread.disappearingMessagesConfiguration.fetchOne(db) else {
throw GRDBStorageError.objectNotFound
throw StorageError.objectNotFound
}
guard let encryptionKeyPair: ClosedGroupKeyPair = try closedGroup.fetchLatestKeyPair(db) else {
throw GRDBStorageError.objectNotFound
throw StorageError.objectNotFound
}
let groupMemberIds: [String] = allGroupMembers
@ -332,9 +330,7 @@ extension MessageSender {
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000))
).inserted(db)
guard let interactionId: Int64 = interaction.id else {
throw GRDBStorageError.objectNotSaved
}
guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
// Send the update to the group
try MessageSender.send(
@ -437,9 +433,7 @@ extension MessageSender {
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000))
).inserted(db)
guard let newInteractionId: Int64 = interaction.id else {
throw GRDBStorageError.objectNotSaved
}
guard let newInteractionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
interactionId = newInteractionId
}
@ -505,7 +499,7 @@ extension MessageSender {
).inserted(db)
guard let interactionId: Int64 = interaction.id else {
throw GRDBStorageError.objectNotSaved
throw StorageError.objectNotSaved
}
// Send the update to the group

View File

@ -10,7 +10,7 @@ extension MessageSender {
// MARK: - Durable
public static func send(_ db: Database, interaction: Interaction, with attachments: [SignalAttachment], in thread: SessionThread) throws {
guard let interactionId: Int64 = interaction.id else { throw GRDBStorageError.objectNotSaved }
guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
try prep(db, signalAttachments: attachments, for: interactionId)
send(
@ -25,7 +25,7 @@ extension MessageSender {
public static func send(_ db: Database, interaction: Interaction, in thread: SessionThread) throws {
// Only 'VisibleMessage' types can be sent via this method
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
guard let interactionId: Int64 = interaction.id else { throw GRDBStorageError.objectNotSaved }
guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
send(
db,
@ -64,7 +64,7 @@ extension MessageSender {
// MARK: - Non-Durable
public static func sendNonDurably(_ db: Database, interaction: Interaction, with attachments: [SignalAttachment], in thread: SessionThread) throws -> Promise<Void> {
guard let interactionId: Int64 = interaction.id else { return Promise(error: GRDBStorageError.objectNotSaved) }
guard let interactionId: Int64 = interaction.id else { return Promise(error: StorageError.objectNotSaved) }
try prep(db, signalAttachments: attachments, for: interactionId)
@ -80,7 +80,7 @@ extension MessageSender {
public static func sendNonDurably(_ db: Database, interaction: Interaction, in thread: SessionThread) throws -> Promise<Void> {
// Only 'VisibleMessage' types can be sent via this method
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
guard let interactionId: Int64 = interaction.id else { throw GRDBStorageError.objectNotSaved }
guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
return sendNonDurably(
db,
@ -174,9 +174,7 @@ extension MessageSender {
// If we don't have a userKeyPair yet then there is no need to sync the configuration
// as the user doesn't exist yet (this will get triggered on the first launch of a
// fresh install due to the migrations getting run)
guard Identity.userExists(db) else {
return Promise(error: GRDBStorageError.generic)
}
guard Identity.userExists(db) else { return Promise(error: StorageError.generic) }
let destination: Message.Destination = Message.Destination.contact(
publicKey: getUserHexEncodedPublicKey(db)
@ -188,7 +186,7 @@ extension MessageSender {
try MessageSender
.sendImmediate(db, message: configurationMessage, to: destination, interactionId: nil)
.done { seal.fulfill(()) }
.catch { _ in seal.reject(GRDBStorageError.generic) }
.catch { _ in seal.reject(StorageError.generic) }
.retainUntilComplete()
}
else {

View File

@ -782,7 +782,7 @@ public extension SessionThreadViewModel {
.defaulting(to: FTS5Pattern(matchingAnyTokenIn: searchTerm))
)
guard let pattern: FTS5Pattern = maybePattern else { throw GRDBStorageError.invalidSearchPattern }
guard let pattern: FTS5Pattern = maybePattern else { throw StorageError.invalidSearchPattern }
return pattern
}

View File

@ -1,39 +0,0 @@
#import <Foundation/Foundation.h>
@class OWSAudioSession;
@class OWSPreferences;
@class OWSWindowManager;
@protocol OWSProximityMonitoringManager;
/**
*
* Environment is a data and data accessor class.
* It handles application-level component wiring in order to support mocks for testing.
* It also handles network configuration for testing/deployment server configurations.
*
**/
@interface Environment : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithAudioSession:(OWSAudioSession *)audioSession
preferences:(OWSPreferences *)preferences
proximityMonitoringManager:(id<OWSProximityMonitoringManager>)proximityMonitoringManager
windowManager:(OWSWindowManager *)windowManager;
@property (nonatomic, readonly) OWSAudioSession *audioSession;
@property (nonatomic, readonly) id<OWSProximityMonitoringManager> proximityMonitoringManager;
@property (nonatomic, readonly) OWSPreferences *preferences;
@property (nonatomic, readonly) OWSWindowManager *windowManager;
// We don't want to cover the window when we request the photo library permission
@property (nonatomic, readwrite) BOOL isRequestingPermission;
@property (class, nonatomic) Environment *shared;
#ifdef DEBUG
// Should only be called by tests.
+ (void)clearSharedForTests;
#endif
@end

View File

@ -1,62 +0,0 @@
#import <Foundation/Foundation.h>
#import "OWSWindowManager.h"
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
#import "OWSPreferences.h"
static Environment *sharedEnvironment = nil;
@interface Environment ()
@property (nonatomic) OWSAudioSession *audioSession;
@property (nonatomic) OWSPreferences *preferences;
@property (nonatomic) id<OWSProximityMonitoringManager> proximityMonitoringManager;
@property (nonatomic) OWSWindowManager *windowManager;
@end
#pragma mark -
@implementation Environment
+ (Environment *)shared
{
return sharedEnvironment;
}
+ (void)setShared:(Environment *)environment
{
// The main app environment should only be set once.
//
// App extensions may be opened multiple times in the same process,
// so statics will persist.
sharedEnvironment = environment;
}
+ (void)clearSharedForTests
{
sharedEnvironment = nil;
}
- (instancetype)initWithAudioSession:(OWSAudioSession *)audioSession
preferences:(OWSPreferences *)preferences
proximityMonitoringManager:(id<OWSProximityMonitoringManager>)proximityMonitoringManager
windowManager:(OWSWindowManager *)windowManager
{
self = [super init];
if (!self) {
return self;
}
_audioSession = audioSession;
_preferences = preferences;
_proximityMonitoringManager = proximityMonitoringManager;
_windowManager = windowManager;
_isRequestingPermission = false;
return self;
}
@end

View File

@ -0,0 +1,76 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SessionUtilitiesKit
public class Environment {
public static var shared: Environment!
public let primaryStorage: OWSPrimaryStorage
public let reachabilityManager: SSKReachabilityManager
public let audioSession: OWSAudioSession
public let preferences: OWSPreferences
public let proximityMonitoringManager: OWSProximityMonitoringManager
public let windowManager: OWSWindowManager
public var isRequestingPermission: Bool
// Note: This property is configured after Environment is created.
public let notificationsManager: Atomic<NotificationsProtocol?> = Atomic(nil)
public var isComplete: Bool {
(notificationsManager.wrappedValue != nil)
}
public var objectReadWriteConnection: YapDatabaseConnection
public var sessionStoreDBConnection: YapDatabaseConnection
public var migrationDBConnection: YapDatabaseConnection
public var analyticsDBConnection: YapDatabaseConnection
// MARK: - Initialization
public init(
primaryStorage: OWSPrimaryStorage,
reachabilityManager: SSKReachabilityManager,
audioSession: OWSAudioSession,
preferences: OWSPreferences,
proximityMonitoringManager: OWSProximityMonitoringManager,
windowManager: OWSWindowManager
) {
self.primaryStorage = primaryStorage
self.reachabilityManager = reachabilityManager
self.audioSession = audioSession
self.preferences = preferences
self.proximityMonitoringManager = proximityMonitoringManager
self.windowManager = windowManager
self.isRequestingPermission = false
self.objectReadWriteConnection = primaryStorage.newDatabaseConnection()
self.sessionStoreDBConnection = primaryStorage.newDatabaseConnection()
self.migrationDBConnection = primaryStorage.newDatabaseConnection()
self.analyticsDBConnection = primaryStorage.newDatabaseConnection()
if Environment.shared == nil {
Environment.shared = self
}
}
// MARK: - Functions
public static func clearSharedForTests() {
shared = nil
}
}
// MARK: - Objective C Support
@objc(SMKEnvironment)
class SMKEnvironment: NSObject {
@objc public static let shared: SMKEnvironment = SMKEnvironment()
@objc public var primaryStorage: OWSPrimaryStorage { Environment.shared.primaryStorage }
@objc public var audioSession: OWSAudioSession { Environment.shared.audioSession }
@objc public var windowManager: OWSWindowManager { Environment.shared.windowManager }
@objc public var isRequestingPermission: Bool { Environment.shared.isRequestingPermission }
}

View File

@ -94,7 +94,7 @@ NS_ASSUME_NONNULL_BEGIN
- (OWSAudioSession *)audioSession
{
return Environment.shared.audioSession;
return SMKEnvironment.shared.audioSession;
}
#pragma mark

View File

@ -4,6 +4,7 @@
#import "OWSWindowManager.h"
#import "Environment.h"
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
#import <SessionUtilitiesKit/SessionUtilitiesKit.h>
NS_ASSUME_NONNULL_BEGIN
@ -153,7 +154,7 @@ const UIWindowLevel UIWindowLevel_MessageActions(void)
+ (instancetype)sharedManager
{
return Environment.shared.windowManager;
return SMKEnvironment.shared.windowManager;
}
- (instancetype)initDefault

View File

@ -1,51 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SessionUtilitiesKit
@objc
public class SSKEnvironment: NSObject {
@objc public let primaryStorage: OWSPrimaryStorage
public let reachabilityManager: SSKReachabilityManager
// Note: This property is configured after Environment is created.
public let notificationsManager: Atomic<NotificationsProtocol?> = Atomic(nil)
@objc public static var shared: SSKEnvironment!
public var isComplete: Bool {
(notificationsManager.wrappedValue != nil)
}
public var objectReadWriteConnection: YapDatabaseConnection
public var sessionStoreDBConnection: YapDatabaseConnection
public var migrationDBConnection: YapDatabaseConnection
public var analyticsDBConnection: YapDatabaseConnection
// MARK: - Initialization
@objc public init(
primaryStorage: OWSPrimaryStorage,
reachabilityManager: SSKReachabilityManager
) {
self.primaryStorage = primaryStorage
self.reachabilityManager = reachabilityManager
self.objectReadWriteConnection = primaryStorage.newDatabaseConnection()
self.sessionStoreDBConnection = primaryStorage.newDatabaseConnection()
self.migrationDBConnection = primaryStorage.newDatabaseConnection()
self.analyticsDBConnection = primaryStorage.newDatabaseConnection()
super.init()
if SSKEnvironment.shared == nil {
SSKEnvironment.shared = self
}
}
// MARK: - Functions
public static func clearSharedForTests() {
shared = nil
}
}

View File

@ -115,12 +115,12 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
guard GRDBStorage.shared[.isReadyForAppExtensions] else { return completeSilenty() }
AppSetup.setupEnvironment(
appSpecificSingletonBlock: {
SSKEnvironment.shared.notificationsManager.mutate {
appSpecificBlock: {
Environment.shared.notificationsManager.mutate {
$0 = NSENotificationPresenter()
}
},
migrationCompletion: { [weak self] _, needsConfigSync in
migrationsCompletion: { [weak self] _, needsConfigSync in
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
completion()
}

View File

@ -43,14 +43,12 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
}
AppSetup.setupEnvironment(
appSpecificSingletonBlock: {
SSKEnvironment.shared.notificationsManager.mutate {
appSpecificBlock: {
Environment.shared.notificationsManager.mutate {
$0 = NoopNotificationsManager()
}
},
migrationCompletion: { [weak self] _, needsConfigSync in
AssertIsOnMainThread()
migrationsCompletion: { [weak self] _, needsConfigSync in
// performUpdateCheck must be invoked after Environment has been initialized because
// upgrade process may depend on Environment.
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)

View File

@ -6,6 +6,8 @@ import SessionUtilitiesKit
enum _001_InitialSetupMigration: Migration {
static let identifier: String = "initialSetup"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static func migrate(_ db: Database) throws {
try db.create(table: Snode.self) { t in

View File

@ -8,6 +8,8 @@ import SessionUtilitiesKit
/// before running the `YDBToGRDBMigration`
enum _002_SetupStandardJobs: Migration {
static let identifier: String = "SetupStandardJobs"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static func migrate(_ db: Database) throws {
try autoreleasepool {

View File

@ -6,6 +6,8 @@ import SessionUtilitiesKit
enum _003_YDBToGRDBMigration: Migration {
static let identifier: String = "YDBToGRDBMigration"
static let minExpectedRunDuration: TimeInterval = 0.2
static let needsConfigSync: Bool = false
static func migrate(_ db: Database) throws {
// MARK: - OnionRequestPath, Snode Pool & Swarm

View File

@ -97,9 +97,9 @@ public extension UIView {
}
@discardableResult
func set(_ dimension: Dimension, to otherDimension: Dimension, of view: UIView, withOffset offset: CGFloat = 0) -> NSLayoutConstraint {
func set(_ dimension: Dimension, to otherDimension: Dimension, of view: UIView, withOffset offset: CGFloat = 0, multiplier: CGFloat = 1) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let otherAnchor: NSLayoutAnchor<NSLayoutDimension> = {
let otherAnchor: NSLayoutDimension = {
switch otherDimension {
case .width: return view.widthAnchor
case .height: return view.heightAnchor
@ -107,8 +107,8 @@ public extension UIView {
}()
let constraint: NSLayoutConstraint = {
switch dimension {
case .width: return widthAnchor.constraint(equalTo: otherAnchor, constant: offset)
case .height: return heightAnchor.constraint(equalTo: otherAnchor, constant: offset)
case .width: return widthAnchor.constraint(equalTo: otherAnchor, multiplier: multiplier, constant: offset)
case .height: return heightAnchor.constraint(equalTo: otherAnchor, multiplier: multiplier, constant: offset)
}
}()
constraint.isActive = true

View File

@ -5,25 +5,8 @@ import GRDB
import PromiseKit
import SignalCoreKit
public enum GRDBStorageError: Error { // TODO: Rename to `StorageError`
case generic
case migrationFailed
case invalidKeySpec
case decodingFailed
case failedToSave
case objectNotFound
case objectNotSaved
case invalidSearchPattern
}
// TODO: Protocol for storage (just need to have 'read' and 'write' methods and mock 'Database'?
// TODO: Rename to `Storage`
public final class GRDBStorage {
public static var shared: GRDBStorage! // TODO: Figure out how/if we want to do this
private static let dbFileName: String = "Session.sqlite"
private static let keychainService: String = "TSKeyChainService"
private static let dbCipherKeySpecKey: String = "GRDBDatabaseCipherKeySpec"
@ -40,14 +23,17 @@ public final class GRDBStorage {
return true
}
private let dbPool: DatabasePool
private let migrator: DatabaseMigrator
public static let shared: GRDBStorage = GRDBStorage()
public private(set) var isValid: Bool = false
public private(set) var hasCompletedMigrations: Bool = false
private var dbPool: DatabasePool?
private var migrator: DatabaseMigrator?
private var migrationProgressUpdater: Atomic<((String, CGFloat) -> ())>?
// MARK: - Initialization
public init?(
migrations: [TargetMigrations]
) throws {
public init() {
print("RAWR START \("\(GRDBStorage.sharedDatabaseDirectoryPath)/\(GRDBStorage.dbFileName)")")
GRDBStorage.deleteDatabaseFiles() // TODO: Remove this
try! GRDBStorage.deleteDbKeys() // TODO: Remove this
@ -76,7 +62,7 @@ public final class GRDBStorage {
// using explicit BLOB syntax, e.g.:
//
// x'98483C6EB40B6C31A448C22A66DED3B5E5E8D5119CAC8327B655C8B5C483648101010101010101010101010101010101'
keySpec = try (keySpec.toHexString().data(using: .utf8) ?? { throw GRDBStorageError.invalidKeySpec }())
keySpec = try (keySpec.toHexString().data(using: .utf8) ?? { throw StorageError.invalidKeySpec }())
keySpec.insert(contentsOf: [120, 39], at: 0) // "x'" prefix
keySpec.append(39) // "'" suffix
@ -90,40 +76,111 @@ public final class GRDBStorage {
try db.execute(sql: "PRAGMA cipher_plaintext_header_size = 32")
}
// Create the DatabasePool to allow us to connect to the database
dbPool = try DatabasePool(
path: "\(GRDBStorage.sharedDatabaseDirectoryPath)/\(GRDBStorage.dbFileName)",
configuration: config
)
// Create the DatabasePool to allow us to connect to the database and mark the storage as valid
do {
dbPool = try DatabasePool(
path: "\(GRDBStorage.sharedDatabaseDirectoryPath)/\(GRDBStorage.dbFileName)",
configuration: config
)
isValid = true
}
catch {}
}
// MARK: - Migrations
public func perform(
migrations: [TargetMigrations],
onProgressUpdate: ((CGFloat, TimeInterval) -> ())?,
onComplete: @escaping (Bool, Bool) -> ()
) {
guard isValid, let dbPool: DatabasePool = dbPool else { return }
typealias MigrationInfo = (identifier: TargetMigrations.Identifier, migrations: TargetMigrations.MigrationSet)
let sortedMigrationInfo: [MigrationInfo] = migrations
.sorted()
.reduce(into: [[MigrationInfo]]()) { result, next in
next.migrations.enumerated().forEach { index, migrationSet in
if result.count <= index {
result.append([])
}
result[index] = (result[index] + [(next.identifier, migrationSet)])
}
}
.reduce(into: []) { result, next in result.append(contentsOf: next) }
// Setup and run any required migrations
migrator = {
var migrator: DatabaseMigrator = DatabaseMigrator()
migrations
.sorted()
.reduce(into: [[(identifier: TargetMigrations.Identifier, migrations: TargetMigrations.MigrationSet)]]()) { result, next in
next.migrations.enumerated().forEach { index, migrationSet in
if result.count <= index {
result.append([])
}
result[index] = (result[index] + [(next.identifier, migrationSet)])
}
}
.compactMap { $0 }
.forEach { sortedMigrationInfo in
sortedMigrationInfo.forEach { migrationInfo in
migrationInfo.migrations.forEach { migration in
migrator.registerMigration(migrationInfo.identifier, migration: migration)
}
}
sortedMigrationInfo.forEach { migrationInfo in
migrationInfo.migrations.forEach { migration in
migrator.registerMigration(migrationInfo.identifier, migration: migration)
}
}
return migrator
}()
try! migrator.migrate(dbPool)
GRDBStorage.shared = self // TODO: Fix this
// Determine which migrations need to be performed and gather the relevant settings needed to
// inform the app of progress/states
let completedMigrations: [String] = (try? dbPool.read { db in try migrator?.completedMigrations(db) })
.defaulting(to: [])
let unperformedMigrations: [(key: String, migration: Migration.Type)] = sortedMigrationInfo
.reduce(into: []) { result, next in
next.migrations.forEach { migration in
let key: String = next.identifier.key(with: migration)
guard !completedMigrations.contains(key) else { return }
result.append((key, migration))
}
}
let migrationToDurationMap: [String: TimeInterval] = unperformedMigrations
.reduce(into: [:]) { result, next in
result[next.key] = next.migration.minExpectedRunDuration
}
let unperformedMigrationDurations: [TimeInterval] = unperformedMigrations
.map { _, migration in migration.minExpectedRunDuration }
let totalMinExpectedDuration: TimeInterval = migrationToDurationMap.values.reduce(0, +)
let needsConfigSync: Bool = unperformedMigrations
.contains(where: { _, migration in migration.needsConfigSync })
self.migrationProgressUpdater = Atomic({ targetKey, progress in
guard let migrationIndex: Int = unperformedMigrations.firstIndex(where: { key, _ in key == targetKey }) else {
return
}
let completedExpectedDuration: TimeInterval = (
(migrationIndex > 0 ? unperformedMigrationDurations[0..<migrationIndex].reduce(0, +) : 0) +
(unperformedMigrationDurations[migrationIndex] * progress)
)
let totalProgress: CGFloat = (completedExpectedDuration / totalMinExpectedDuration)
DispatchQueue.main.async {
onProgressUpdate?(totalProgress, totalMinExpectedDuration)
}
})
// If we have an unperformed migration then trigger the progress updater immediately
if let firstMigrationKey: String = unperformedMigrations.first?.key {
self.migrationProgressUpdater?.wrappedValue(firstMigrationKey, 0)
}
self.migrator?.asyncMigrate(dbPool) { [weak self] _, error in
self?.hasCompletedMigrations = true
self?.migrationProgressUpdater = nil
onComplete((error == nil), needsConfigSync)
}
}
public func update(
progress: CGFloat,
for migration: Migration.Type,
in target: TargetMigrations.Identifier
) {
GRDBStorage.shared.migrationProgressUpdater?.wrappedValue(target.key(with: migration), progress)
}
// MARK: - Security
@ -137,7 +194,7 @@ public final class GRDBStorage {
var keySpec: Data = try getDatabaseCipherKeySpec()
defer { keySpec.resetBytes(in: 0..<keySpec.count) }
guard keySpec.count == kSQLCipherKeySpecLength else { throw GRDBStorageError.invalidKeySpec }
guard keySpec.count == kSQLCipherKeySpecLength else { throw StorageError.invalidKeySpec }
return keySpec
}
@ -151,7 +208,7 @@ public final class GRDBStorage {
//errSecInteractionNotAllowed
case (GRDBStorageError.invalidKeySpec, _):
case (StorageError.invalidKeySpec, _):
// For these cases it means either the keySpec or the keychain has become corrupt so in order to
// get back to a "known good state" and behave like a new install we need to reset the storage
// and regenerate the key
@ -227,6 +284,8 @@ public final class GRDBStorage {
// MARK: - Functions
@discardableResult public func write<T>(updates: (Database) throws -> T?) -> T? {
guard isValid, let dbPool: DatabasePool = dbPool else { return nil }
return try? dbPool.write(updates)
}
@ -235,6 +294,8 @@ public final class GRDBStorage {
}
public func writeAsync<T>(updates: @escaping (Database) throws -> T, completion: @escaping (Database, Swift.Result<T, Error>) throws -> Void) {
guard isValid, let dbPool: DatabasePool = dbPool else { return }
dbPool.asyncWrite(
updates,
completion: { db, result in
@ -244,6 +305,8 @@ public final class GRDBStorage {
}
@discardableResult public func read<T>(_ value: (Database) throws -> T?) -> T? {
guard isValid, let dbPool: DatabasePool = dbPool else { return nil }
return try? dbPool.read(value)
}
@ -262,7 +325,9 @@ public final class GRDBStorage {
onError: @escaping (Error) -> Void,
onChange: @escaping (Reducer.Value) -> Void
) -> DatabaseCancellable {
observation.start(
guard isValid, let dbPool: DatabasePool = dbPool else { return AnyDatabaseCancellable(cancel: {}) }
return observation.start(
in: dbPool,
scheduling: scheduler,
onError: onError,
@ -271,6 +336,7 @@ public final class GRDBStorage {
}
public func addObserver(_ observer: TransactionObserver?) {
guard isValid, let dbPool: DatabasePool = dbPool else { return }
guard let observer: TransactionObserver = observer else { return }
dbPool.add(transactionObserver: observer)
@ -282,6 +348,8 @@ public final class GRDBStorage {
public extension GRDBStorage {
// FIXME: Would be good to replace these with Swift Combine
@discardableResult func read<T>(_ value: (Database) throws -> Promise<T>) -> Promise<T> {
guard isValid, let dbPool: DatabasePool = dbPool else { return Promise(error: StorageError.databaseInvalid) }
do {
return try dbPool.read(value)
}
@ -291,6 +359,8 @@ public extension GRDBStorage {
}
@discardableResult func write<T>(updates: (Database) throws -> Promise<T>) -> Promise<T> {
guard isValid, let dbPool: DatabasePool = dbPool else { return Promise(error: StorageError.databaseInvalid) }
do {
return try dbPool.write(updates)
}

View File

@ -5,6 +5,8 @@ import GRDB
enum _001_InitialSetupMigration: Migration {
static let identifier: String = "initialSetup"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static func migrate(_ db: Database) throws {
try db.create(table: Identity.self) { t in

View File

@ -8,6 +8,8 @@ import Curve25519Kit
/// before running the `YDBToGRDBMigration`
enum _002_SetupStandardJobs: Migration {
static let identifier: String = "SetupStandardJobs"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static func migrate(_ db: Database) throws {
try autoreleasepool {

View File

@ -5,6 +5,8 @@ import GRDB
enum _003_YDBToGRDBMigration: Migration {
static let identifier: String = "YDBToGRDBMigration"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static func migrate(_ db: Database) throws {
// MARK: - Identity keys
@ -69,7 +71,7 @@ enum _003_YDBToGRDBMigration: Migration {
return
}
throw GRDBStorageError.migrationFailed
throw StorageError.migrationFailed
}
print("RAWR publicKey \(userX25519KeyPair.publicKey.toHexString())")
try autoreleasepool {

View File

@ -0,0 +1,17 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
public enum StorageError: Error {
case generic
case databaseInvalid
case migrationFailed
case invalidKeySpec
case decodingFailed
case failedToSave
case objectNotFound
case objectNotSaved
case invalidSearchPattern
}

View File

@ -5,6 +5,8 @@ import GRDB
public protocol Migration {
static var identifier: String { get }
static var needsConfigSync: Bool { get }
static var minExpectedRunDuration: TimeInterval { get }
static func migrate(_ db: Database) throws
}

View File

@ -26,6 +26,10 @@ public struct TargetMigrations: Comparable {
return (lhsIndex < rhsIndex)
}
func key(with migration: Migration.Type) -> String {
return "\(self.rawValue).\(migration.identifier)"
}
}
public typealias MigrationSet = [Migration.Type]

View File

@ -3,8 +3,6 @@ import SessionSnodeKit
extension OWSPrimaryStorage : OWSPrimaryStorageProtocol { }
var isSetup: Bool = false // TODO: Remove this
@objc(SNConfiguration)
public final class Configuration : NSObject {
@ -19,21 +17,4 @@ public final class Configuration : NSObject {
SNMessagingKit.configure(storage: Storage.shared)
SNSnodeKit.configure()
}
@objc public static func performDatabaseSetup() {
if !isSetup {
isSetup = true
// TODO: Need to store this result somewhere?
// TODO: This function seems to get called multiple times
//DispatchQueue.main.once
let storage: GRDBStorage? = try? GRDBStorage(
migrations: [
SNUtilitiesKit.migrations(),
SNSnodeKit.migrations(),
SNMessagingKit.migrations()
]
)
}
}
}

View File

@ -1,17 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
typedef void (^OWSDatabaseMigrationCompletion)(BOOL success, BOOL requiresConfigurationSync);
// This is _NOT_ a singleton and will be instantiated each time that the SAE is used.
@interface AppSetup : NSObject
+ (void)setupEnvironmentWithAppSpecificSingletonBlock:(dispatch_block_t)appSpecificSingletonBlock
migrationCompletion:(OWSDatabaseMigrationCompletion)migrationCompletion;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,93 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "AppSetup.h"
#import "Environment.h"
#import "VersionMigrations.h"
#import <SignalUtilitiesKit/OWSDatabaseMigration.h>
#import <SessionMessagingKit/OWSBackgroundTask.h>
#import <SessionMessagingKit/OWSStorage.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@implementation AppSetup
+ (void)setupEnvironmentWithAppSpecificSingletonBlock:(dispatch_block_t)appSpecificSingletonBlock
migrationCompletion:(OWSDatabaseMigrationCompletion)migrationCompletion
{
OWSAssertDebug(appSpecificSingletonBlock);
OWSAssertDebug(migrationCompletion);
__block OWSBackgroundTask *_Nullable backgroundTask =
[OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Order matters here.
//
// All of these "singletons" should have any dependencies used in their
// initializers injected.
[[OWSBackgroundTaskManager sharedManager] observeNotifications];
OWSPrimaryStorage *primaryStorage = [[OWSPrimaryStorage alloc] initStorage];
[OWSPrimaryStorage protectFiles];
// AFNetworking (via CFNetworking) spools it's attachments to NSTemporaryDirectory().
// If you receive a media message while the device is locked, the download will fail if the temporary directory
// is NSFileProtectionComplete
BOOL success = [OWSFileSystem protectFileOrFolderAtPath:NSTemporaryDirectory()
fileProtectionType:NSFileProtectionCompleteUntilFirstUserAuthentication];
OWSAssert(success);
OWSPreferences *preferences = [OWSPreferences new];
id<SSKReachabilityManager> reachabilityManager = [SSKReachabilityManagerImpl new];
OWSAudioSession *audioSession = [OWSAudioSession new];
id<OWSProximityMonitoringManager> proximityMonitoringManager = [OWSProximityMonitoringManagerImpl new];
OWSWindowManager *windowManager = [[OWSWindowManager alloc] initDefault];
[Environment setShared:[[Environment alloc] initWithAudioSession:audioSession
preferences:preferences
proximityMonitoringManager:proximityMonitoringManager
windowManager:windowManager]];
// TODO: Add this back
// TODO: Refactor this file to Swift
[SSKEnvironment setShared:[[SSKEnvironment alloc] initWithPrimaryStorage:primaryStorage
reachabilityManager:reachabilityManager]];
appSpecificSingletonBlock();
// TODO: Add this back?
// OWSAssertDebug(SSKEnvironment.shared.isComplete);
[SNConfiguration performMainSetup]; // Must happen before the performUpdateCheck call below
// Register renamed classes.
[NSKeyedUnarchiver setClass:[OWSDatabaseMigration class] forClassName:[OWSDatabaseMigration collection]];
[OWSStorage registerExtensionsWithMigrationBlock:^() {
dispatch_async(dispatch_get_main_queue(), ^{
// Don't start database migrations until storage is ready.
[VersionMigrations performUpdateCheckWithCompletion:^(BOOL successful, BOOL needsConfigSync) {
OWSAssertIsOnMainThread();
migrationCompletion(successful, needsConfigSync);
OWSAssertDebug(backgroundTask);
backgroundTask = nil;
}];
});
}];
// Must happen after the performUpdateCheck above to ensure all legacy database migrations have run
[SNConfiguration performDatabaseSetup];
});
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,71 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SessionMessagingKit
import SessionUtilitiesKit
import UIKit
public enum AppSetup {
private static var hasRun: Bool = false
public static func setupEnvironment(
appSpecificBlock: @escaping () -> (),
migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil,
migrationsCompletion: @escaping (Bool, Bool) -> ()
) {
guard !AppSetup.hasRun else { return }
AppSetup.hasRun = true
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(labelStr: #function)
DispatchQueue.global(qos: .userInitiated).async {
// Order matters here.
//
// All of these "singletons" should have any dependencies used in their
// initializers injected.
OWSBackgroundTaskManager.shared().observeNotifications()
let primaryStorage: OWSPrimaryStorage = OWSPrimaryStorage(storage: ())
OWSPrimaryStorage.protectFiles()
// AFNetworking (via CFNetworking) spools it's attachments to NSTemporaryDirectory().
// If you receive a media message while the device is locked, the download will fail if the temporary directory
// is NSFileProtectionComplete
let success: Bool = OWSFileSystem.protectFileOrFolder(
atPath: NSTemporaryDirectory(),
fileProtectionType: .completeUntilFirstUserAuthentication
)
assert(success)
Environment.shared = Environment(
primaryStorage: primaryStorage,
reachabilityManager: SSKReachabilityManagerImpl(),
audioSession: OWSAudioSession(),
preferences: OWSPreferences(),
proximityMonitoringManager: OWSProximityMonitoringManagerImpl(),
windowManager: OWSWindowManager(default: ())
)
appSpecificBlock()
/// `performMainSetup` **MUST** run before `perform(migrations:)`
Configuration.performMainSetup()
GRDBStorage.shared.perform(
migrations: [
SNUtilitiesKit.migrations(),
SNSnodeKit.migrations(),
SNMessagingKit.migrations()
],
onProgressUpdate: migrationProgressChanged,
onComplete: { success, needsConfigSync in
DispatchQueue.main.async {
migrationsCompletion(success, needsConfigSync)
// The 'if' is only there to prevent the "variable never read" warning from showing
if backgroundTask != nil { backgroundTask = nil }
}
}
)
}
}
}

View File

@ -1,25 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
#define RECENT_CALLS_DEFAULT_KEY @"RPRecentCallsDefaultKey"
typedef void (^VersionMigrationCompletion)(BOOL success, BOOL requiresConfigurationSync);
@interface VersionMigrations : NSObject
+ (void)performUpdateCheckWithCompletion:(VersionMigrationCompletion)completion;
+ (BOOL)isVersion:(NSString *)thisVersionString
atLeast:(NSString *)openLowerBoundVersionString
andLessThan:(NSString *)closedUpperBoundVersionString;
+ (BOOL)isVersion:(NSString *)thisVersionString atLeast:(NSString *)thatVersionString;
+ (BOOL)isVersion:(NSString *)thisVersionString lessThan:(NSString *)thatVersionString;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,126 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "VersionMigrations.h"
#import "OWSDatabaseMigrationRunner.h"
#import <SessionUtilitiesKit/AppContext.h>
#import <SignalUtilitiesKit/AppVersion.h>
#import <SessionUtilitiesKit/NSUserDefaults+OWS.h>
#import <YapDatabase/YapDatabase.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
#define NEEDS_TO_REGISTER_PUSH_KEY @"Register For Push"
#define NEEDS_TO_REGISTER_ATTRIBUTES @"Register Attributes"
@implementation VersionMigrations
#pragma mark - Utility methods
+ (void)performUpdateCheckWithCompletion:(VersionMigrationCompletion)completion
{
OWSLogInfo(@"");
// performUpdateCheck must be invoked after Environment has been initialized because
// upgrade process may depend on Environment.
OWSAssertDebug(Environment.shared);
OWSAssertDebug(completion);
NSString *previousVersion = AppVersion.sharedInstance.lastAppVersion;
NSString *currentVersion = AppVersion.sharedInstance.currentAppVersion;
OWSLogInfo(@"Checking migrations. currentVersion: %@, lastRanVersion: %@", currentVersion, previousVersion);
if (!previousVersion) {
// Note: We need to run the migrations here anyway to ensure that they don't run on subsequent launches
// and result in unexpected data changes (eg. 'MessageRequestsMigration' auto-approves all threads
// if this happens on the 2nd launch then any threads created during the 1st launch which haven't
// been approved would get auto-approved, allowing the user to use contacts which haven't approved
// comms to appear as options when creating closed groups)
OWSLogInfo(@"No previous version found. Probably first launch since install - running migrations so they don't run on second launch.");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[OWSDatabaseMigrationRunner alloc] init] runAllOutstandingWithCompletion:completion];
});
return;
}
if ([self isVersion:previousVersion atLeast:@"2.0.0" andLessThan:@"2.1.70"] && [SUKIdentity userExists]) {
[self clearVideoCache];
}
if ([self isVersion:previousVersion atLeast:@"2.0.0" andLessThan:@"2.3.0"] && [SUKIdentity userExists]) {
[self clearBloomFilterCache];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[OWSDatabaseMigrationRunner alloc] init] runAllOutstandingWithCompletion:completion];
});
}
+ (BOOL)isVersion:(NSString *)thisVersionString
atLeast:(NSString *)openLowerBoundVersionString
andLessThan:(NSString *)closedUpperBoundVersionString
{
return [self isVersion:thisVersionString atLeast:openLowerBoundVersionString] &&
[self isVersion:thisVersionString lessThan:closedUpperBoundVersionString];
}
+ (BOOL)isVersion:(NSString *)thisVersionString atLeast:(NSString *)thatVersionString
{
return [thisVersionString compare:thatVersionString options:NSNumericSearch] != NSOrderedAscending;
}
+ (BOOL)isVersion:(NSString *)thisVersionString lessThan:(NSString *)thatVersionString
{
return [thisVersionString compare:thatVersionString options:NSNumericSearch] == NSOrderedAscending;
}
#pragma mark Upgrading to 2.1 - Removing video cache folder
+ (void)clearVideoCache
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
basePath = [basePath stringByAppendingPathComponent:@"videos"];
NSError *error;
if ([[NSFileManager defaultManager] fileExistsAtPath:basePath]) {
[NSFileManager.defaultManager removeItemAtPath:basePath error:&error];
}
if (error) {
OWSLogError(
@"An error occured while removing the videos cache folder from old location: %@", error.debugDescription);
}
}
#pragma mark Upgrading to 2.3.0
// We removed bloom filter contact discovery. Clean up any local bloom filter data.
+ (void)clearBloomFilterCache
{
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *cachesDir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *bloomFilterPath = [[cachesDir objectAtIndex:0] stringByAppendingPathComponent:@"bloomfilter"];
if ([fm fileExistsAtPath:bloomFilterPath]) {
NSError *deleteError;
if ([fm removeItemAtPath:bloomFilterPath error:&deleteError]) {
OWSLogInfo(@"Successfully removed bloom filter cache.");
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[transaction removeAllObjectsInCollection:@"TSRecipient"];
}];
} else {
OWSLogError(@"Failed to remove bloom filter cache with error: %@", deleteError.localizedDescription);
}
} else {
OWSLogDebug(@"No bloom filter cache to remove.");
}
}
@end
NS_ASSUME_NONNULL_END