Finished of the Conversation screen and JobQueue concurrency

Updated the migrations to indicate progress (Potential to base progress for the "processing" sections on the file size of the legacy database)
Updated the JobRunner to properly support concurrent queues for sending/receiving (other queues are still serial)
Added the typing indicator logic into the ConversationVC
Put code into SUKLegacy for connecting to the YDB database
Fixed a couple of minor UI bugs with the GalleryRailView
Updated the media gallery selection screen to use the appropriate system theme colouring (was painful to randomly swap from dark mode to like for one screen...)
Added an alert for when the database migration fails
Deleted the legacy migrations (manually applying any unapplied changes as part of the YDB to GRDB migration process)
This commit is contained in:
Morgan Pretty 2022-05-31 17:41:02 +10:00
parent 26c7a5022a
commit 8ff542405c
90 changed files with 1192 additions and 1635 deletions

View File

@ -207,7 +207,6 @@
B897621C25D201F7004F83B2 /* ScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */; };
B8AE75A425A6C6A6001A84D2 /* Data+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */; };
B8AF4BB426A5204600583500 /* SendSeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8AF4BB326A5204600583500 /* SendSeedModal.swift */; };
B8B3204E258C15C80020074B /* ContactsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B32044258C117C0020074B /* ContactsMigration.swift */; };
B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B320B6258C30D70020074B /* HTMLMetadata.swift */; };
B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A4238F627000BA5194 /* HomeVC.swift */; };
B8BC00C0257D90E30032E807 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BC00BF257D90E30032E807 /* General.swift */; };
@ -297,8 +296,6 @@
C32C5EE5256DF506003C73A2 /* YapDatabaseConnection+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB5F255A580E00E217F9 /* YapDatabaseConnection+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; };
C32C5EEE256DF54E003C73A2 /* TSDatabaseView.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB46255A580C00E217F9 /* TSDatabaseView.m */; };
C32C5EF7256DF567003C73A2 /* TSDatabaseView.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB2C255A580A00E217F9 /* TSDatabaseView.h */; settings = {ATTRIBUTES = (Public, ); }; };
C32C5FA1256DFED5003C73A2 /* NSArray+Functional.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAB8255A580100E217F9 /* NSArray+Functional.m */; };
C32C5FAA256DFED9003C73A2 /* NSArray+Functional.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB5C255A580E00E217F9 /* NSArray+Functional.h */; settings = {ATTRIBUTES = (Public, ); }; };
C32C5FBB256E0206003C73A2 /* OWSBackgroundTask.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */; };
C32C5FC4256E0209003C73A2 /* OWSBackgroundTask.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB38255A580B00E217F9 /* OWSBackgroundTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB77255A581000E217F9 /* NSUserDefaults+OWS.m */; };
@ -340,14 +337,10 @@
C33FDC50255A582000E217F9 /* OWSDispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDA96255A57FE00E217F9 /* OWSDispatch.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDC53255A582000E217F9 /* OutageDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA99255A57FE00E217F9 /* OutageDetection.swift */; };
C33FDC58255A582000E217F9 /* ReverseDispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */; };
C33FDC64255A582000E217F9 /* NSObject+Casting.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAAA255A580000E217F9 /* NSObject+Casting.m */; };
C33FDC78255A582000E217F9 /* TSConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDABE255A580100E217F9 /* TSConstants.m */; };
C33FDC7B255A582000E217F9 /* NSSet+Functional.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAC1255A580100E217F9 /* NSSet+Functional.m */; };
C33FDC7D255A582000E217F9 /* OWSDispatch.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAC3255A580200E217F9 /* OWSDispatch.m */; };
C33FDC96255A582000E217F9 /* NSObject+Casting.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDADC255A580400E217F9 /* NSObject+Casting.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDC98255A582000E217F9 /* SwiftSingletons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDADE255A580400E217F9 /* SwiftSingletons.swift */; };
C33FDC9A255A582000E217F9 /* ByteParser.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAE0255A580400E217F9 /* ByteParser.m */; };
C33FDCC7255A582000E217F9 /* NSArray+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB0D255A580800E217F9 /* NSArray+OWS.m */; };
C33FDCD1255A582000E217F9 /* FunctionalUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB17255A580800E217F9 /* FunctionalUtil.m */; };
C33FDCFA255A582000E217F9 /* SignalIOSProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB40255A580C00E217F9 /* SignalIOSProto.swift */; };
C33FDD03255A582000E217F9 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB49255A580C00E217F9 /* WeakTimer.swift */; };
@ -362,9 +355,7 @@
C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */; };
C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD8255A581900E217F9 /* SignalIOS.pb.swift */; };
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, ); }; };
C33FDDB8255A582000E217F9 /* NSSet+Functional.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBFE255A581C00E217F9 /* NSSet+Functional.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDC03255A581D00E217F9 /* ByteParser.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDDC5255A582000E217F9 /* OWSError.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC0B255A581D00E217F9 /* OWSError.m */; };
C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDC12255A581E00E217F9 /* TSConstants.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -396,7 +387,6 @@
C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C374EEEA25DA3CA70073A857 /* ConversationTitleView.swift */; };
C374EEF425DB31D40073A857 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C374EEF325DB31D40073A857 /* VoiceMessageRecordingView.swift */; };
C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C379DCF3256735770002D4EB /* VisibleMessage+Attachment.swift */; };
C37F5396255B95BD002AEA92 /* OWSAnyTouchGestureRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF302255B6DBE007E1867 /* OWSAnyTouchGestureRecognizer.h */; settings = {ATTRIBUTES = (Public, ); }; };
C37F5414255BAFA7002AEA92 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; };
C37F54DC255BB84A002AEA92 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; };
C38D5E8D2575011E00B6A65C /* MessageSender+ClosedGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38D5E8C2575011E00B6A65C /* MessageSender+ClosedGroups.swift */; };
@ -413,19 +403,12 @@
C38EF24D255B6D67007E1867 /* UIView+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF240255B6D67007E1867 /* UIView+OWS.swift */; };
C38EF24E255B6D67007E1867 /* Collection+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF241255B6D67007E1867 /* Collection+OWS.swift */; };
C38EF24F255B6D67007E1867 /* UIColor+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF242255B6D67007E1867 /* UIColor+OWS.m */; };
C38EF272255B6D7A007E1867 /* OWSResaveCollectionDBMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF26C255B6D79007E1867 /* OWSResaveCollectionDBMigration.m */; };
C38EF273255B6D7A007E1867 /* OWSDatabaseMigrationRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF26D255B6D79007E1867 /* OWSDatabaseMigrationRunner.m */; };
C38EF274255B6D7A007E1867 /* OWSResaveCollectionDBMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF26E255B6D79007E1867 /* OWSResaveCollectionDBMigration.h */; settings = {ATTRIBUTES = (Public, ); }; };
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, ); }; };
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 */; };
C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */; };
C38EF2B4255B6D9C007E1867 /* UIView+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B2255B6D9C007E1867 /* UIView+Utilities.swift */; };
C38EF30C255B6DBF007E1867 /* OWSScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */; };
C38EF31A255B6DBF007E1867 /* OWSAnyTouchGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F0255B6DBB007E1867 /* OWSAnyTouchGestureRecognizer.m */; };
C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.swift */; };
C38EF31D255B6DBF007E1867 /* UIImage+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */; };
C38EF324255B6DBF007E1867 /* Bench.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2FA255B6DBD007E1867 /* Bench.swift */; };
@ -650,10 +633,8 @@
FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */; };
FD17D7EA27F6A1C600122BE0 /* SUKLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E927F6A1C600122BE0 /* SUKLegacy.swift */; };
FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */; };
FD28A4F227E990E800FF65E7 /* BlockingManagerRemovalMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */; };
FD28A4F427EA79F800FF65E7 /* BlockListUIUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */; };
FD3AABE928306BBD00E5099A /* ThreadPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */; };
FD3C907327E8387300CD579F /* OpenGroupServerIdLookupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */; };
FD3C907527E83AC200CD579F /* OpenGroupServerIdLookup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */; };
FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; };
FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4B200D283492210034334B /* InsetLockableTableView.swift */; };
@ -680,7 +661,6 @@
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 */; };
FD90040F2818AB6D00ABAAF6 /* GetSnodePoolJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */; };
FD9004122818ABDC00ABAAF6 /* Job.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73F280402C4004C14C5 /* Job.swift */; };
FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; };
@ -1139,7 +1119,6 @@
B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToBottomButton.swift; sourceTree = "<group>"; };
B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Trimming.swift"; sourceTree = "<group>"; };
B8AF4BB326A5204600583500 /* SendSeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendSeedModal.swift; sourceTree = "<group>"; };
B8B32044258C117C0020074B /* ContactsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsMigration.swift; sourceTree = "<group>"; };
B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = "<group>"; };
B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -1226,14 +1205,10 @@
C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseDispatchQueue.swift; sourceTree = "<group>"; };
C33FDAA1255A57FF00E217F9 /* TSYapDatabaseObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSYapDatabaseObject.h; sourceTree = "<group>"; };
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = "<group>"; };
C33FDAAA255A580000E217F9 /* NSObject+Casting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Casting.m"; sourceTree = "<group>"; };
C33FDAB1255A580000E217F9 /* OWSStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSStorage.m; sourceTree = "<group>"; };
C33FDAB8255A580100E217F9 /* NSArray+Functional.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Functional.m"; sourceTree = "<group>"; };
C33FDAB9255A580100E217F9 /* OWSStorage+Subclass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OWSStorage+Subclass.h"; sourceTree = "<group>"; };
C33FDABE255A580100E217F9 /* TSConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSConstants.m; sourceTree = "<group>"; };
C33FDAC1255A580100E217F9 /* NSSet+Functional.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSSet+Functional.m"; sourceTree = "<group>"; };
C33FDAC3255A580200E217F9 /* OWSDispatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDispatch.m; sourceTree = "<group>"; };
C33FDADC255A580400E217F9 /* NSObject+Casting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+Casting.h"; sourceTree = "<group>"; };
C33FDADE255A580400E217F9 /* SwiftSingletons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftSingletons.swift; sourceTree = "<group>"; };
C33FDAE0255A580400E217F9 /* ByteParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ByteParser.m; sourceTree = "<group>"; };
C33FDAEA255A580500E217F9 /* OWSBackupFragment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupFragment.h; sourceTree = "<group>"; };
@ -1245,7 +1220,6 @@
C33FDAFE255A580600E217F9 /* OWSStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSStorage.h; sourceTree = "<group>"; };
C33FDB01255A580700E217F9 /* AppReadiness.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppReadiness.h; sourceTree = "<group>"; };
C33FDB07255A580700E217F9 /* OWSBackupFragment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupFragment.m; sourceTree = "<group>"; };
C33FDB0D255A580800E217F9 /* NSArray+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+OWS.m"; sourceTree = "<group>"; };
C33FDB12255A580800E217F9 /* NSString+SSK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SSK.h"; sourceTree = "<group>"; };
C33FDB14255A580800E217F9 /* OWSMath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMath.h; sourceTree = "<group>"; };
C33FDB17255A580800E217F9 /* FunctionalUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FunctionalUtil.m; sourceTree = "<group>"; };
@ -1271,7 +1245,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>"; };
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>"; };
C33FDB5F255A580E00E217F9 /* YapDatabaseConnection+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "YapDatabaseConnection+OWS.h"; sourceTree = "<group>"; };
C33FDB68255A580F00E217F9 /* ContentProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentProxy.swift; sourceTree = "<group>"; };
C33FDB69255A580F00E217F9 /* FeatureFlags.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureFlags.swift; sourceTree = "<group>"; };
@ -1298,9 +1271,7 @@
C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotificationAPI.swift; sourceTree = "<group>"; };
C33FDBE1255A581A00E217F9 /* LKGroupUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LKGroupUtilities.m; 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>"; };
C33FDBF9255A581C00E217F9 /* OWSError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSError.h; sourceTree = "<group>"; };
C33FDBFE255A581C00E217F9 /* NSSet+Functional.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSSet+Functional.h"; sourceTree = "<group>"; };
C33FDC02255A581D00E217F9 /* OWSPrimaryStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSPrimaryStorage.m; sourceTree = "<group>"; };
C33FDC03255A581D00E217F9 /* ByteParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ByteParser.h; sourceTree = "<group>"; };
C33FDC0B255A581D00E217F9 /* OWSError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSError.m; sourceTree = "<group>"; };
@ -1351,12 +1322,6 @@
C38EF240255B6D67007E1867 /* UIView+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+OWS.swift"; path = "SignalUtilitiesKit/Utilities/UIView+OWS.swift"; sourceTree = SOURCE_ROOT; };
C38EF241255B6D67007E1867 /* Collection+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Collection+OWS.swift"; path = "SignalUtilitiesKit/Utilities/Collection+OWS.swift"; sourceTree = SOURCE_ROOT; };
C38EF242255B6D67007E1867 /* UIColor+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIColor+OWS.m"; path = "SignalUtilitiesKit/Utilities/UIColor+OWS.m"; sourceTree = SOURCE_ROOT; };
C38EF26C255B6D79007E1867 /* OWSResaveCollectionDBMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSResaveCollectionDBMigration.m; path = SignalUtilitiesKit/Database/Migrations/OWSResaveCollectionDBMigration.m; sourceTree = SOURCE_ROOT; };
C38EF26D255B6D79007E1867 /* OWSDatabaseMigrationRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDatabaseMigrationRunner.m; path = SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigrationRunner.m; sourceTree = SOURCE_ROOT; };
C38EF26E255B6D79007E1867 /* OWSResaveCollectionDBMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSResaveCollectionDBMigration.h; path = SignalUtilitiesKit/Database/Migrations/OWSResaveCollectionDBMigration.h; sourceTree = SOURCE_ROOT; };
C38EF26F255B6D79007E1867 /* OWSDatabaseMigrationRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSDatabaseMigrationRunner.h; path = SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigrationRunner.h; sourceTree = SOURCE_ROOT; };
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; };
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; };
@ -1366,7 +1331,6 @@
C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSScreenLock.swift; path = "SignalUtilitiesKit/Screen Lock/OWSScreenLock.swift"; sourceTree = SOURCE_ROOT; };
C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProximityMonitoringManager.swift; path = SessionMessagingKit/Utilities/ProximityMonitoringManager.swift; sourceTree = SOURCE_ROOT; };
C38EF2EF255B6DBB007E1867 /* Weak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Weak.swift; path = SessionUtilitiesKit/General/Weak.swift; sourceTree = SOURCE_ROOT; };
C38EF2F0255B6DBB007E1867 /* OWSAnyTouchGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSAnyTouchGestureRecognizer.m; path = SignalUtilitiesKit/Utilities/OWSAnyTouchGestureRecognizer.m; sourceTree = SOURCE_ROOT; };
C38EF2F1255B6DBB007E1867 /* OWSPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSPreferences.h; path = SessionMessagingKit/Utilities/OWSPreferences.h; sourceTree = SOURCE_ROOT; };
C38EF2F2255B6DBC007E1867 /* Searcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Searcher.swift; path = SignalUtilitiesKit/Utilities/Searcher.swift; sourceTree = SOURCE_ROOT; };
C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIImage+OWS.swift"; path = "SignalUtilitiesKit/Utilities/UIImage+OWS.swift"; sourceTree = SOURCE_ROOT; };
@ -1376,7 +1340,6 @@
C38EF2FB255B6DBD007E1867 /* OWSWindowManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSWindowManager.h; path = SessionMessagingKit/Utilities/OWSWindowManager.h; sourceTree = SOURCE_ROOT; };
C38EF300255B6DBD007E1867 /* UIUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UIUtil.m; path = SignalUtilitiesKit/Utilities/UIUtil.m; sourceTree = SOURCE_ROOT; };
C38EF301255B6DBD007E1867 /* OWSFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSFormat.h; path = SignalUtilitiesKit/Utilities/OWSFormat.h; sourceTree = SOURCE_ROOT; };
C38EF302255B6DBE007E1867 /* OWSAnyTouchGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSAnyTouchGestureRecognizer.h; path = SignalUtilitiesKit/Utilities/OWSAnyTouchGestureRecognizer.h; sourceTree = SOURCE_ROOT; };
C38EF304255B6DBE007E1867 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageCache.swift; path = SignalUtilitiesKit/Utilities/ImageCache.swift; sourceTree = SOURCE_ROOT; };
C38EF305255B6DBE007E1867 /* OWSFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSFormat.m; path = SignalUtilitiesKit/Utilities/OWSFormat.m; sourceTree = SOURCE_ROOT; };
C38EF306255B6DBE007E1867 /* OWSWindowManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSWindowManager.m; path = SessionMessagingKit/Utilities/OWSWindowManager.m; sourceTree = SOURCE_ROOT; };
@ -1611,11 +1574,9 @@
FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
FD17D7E927F6A1C600122BE0 /* SUKLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SUKLegacy.swift; sourceTree = "<group>"; };
FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Utilities.swift"; sourceTree = "<group>"; };
FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockingManagerRemovalMigration.swift; sourceTree = "<group>"; };
FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListUIUtils.swift; sourceTree = "<group>"; };
FD28A4F527EAD44C00FF65E7 /* GRDBStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRDBStorage.swift; sourceTree = "<group>"; };
FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerViewModel.swift; sourceTree = "<group>"; };
FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookupMigration.swift; sourceTree = "<group>"; };
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = "<group>"; };
FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModel.swift; sourceTree = "<group>"; };
FD4B200D283492210034334B /* InsetLockableTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLockableTableView.swift; sourceTree = "<group>"; };
@ -1642,7 +1603,6 @@
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>"; };
FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = "<group>"; };
FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = "<group>"; };
FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = "<group>"; };
@ -2203,8 +2163,6 @@
B8BC00BF257D90E30032E807 /* General.swift */,
C3C2A5CE2553860700C340D1 /* Logging.swift */,
C33FDAFD255A580600E217F9 /* LRUCache.swift */,
C33FDB5C255A580E00E217F9 /* NSArray+Functional.h */,
C33FDAB8255A580100E217F9 /* NSArray+Functional.m */,
C33FDB3B255A580B00E217F9 /* NSNotificationCenter+OWS.h */,
C33FDB6C255A580F00E217F9 /* NSNotificationCenter+OWS.m */,
C33FDA7A255A57FB00E217F9 /* NSRegularExpression+SSK.swift */,
@ -2749,16 +2707,6 @@
C379DCE82567330E0002D4EB /* Migrations */ = {
isa = PBXGroup;
children = (
B8B32044258C117C0020074B /* ContactsMigration.swift */,
FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */,
FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */,
FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */,
C38EF271255B6D79007E1867 /* OWSDatabaseMigration.h */,
C38EF270255B6D79007E1867 /* OWSDatabaseMigration.m */,
C38EF26F255B6D79007E1867 /* OWSDatabaseMigrationRunner.h */,
C38EF26D255B6D79007E1867 /* OWSDatabaseMigrationRunner.m */,
C38EF26E255B6D79007E1867 /* OWSResaveCollectionDBMigration.h */,
C38EF26C255B6D79007E1867 /* OWSResaveCollectionDBMigration.m */,
);
path = Migrations;
sourceTree = "<group>";
@ -3024,8 +2972,6 @@
isa = PBXGroup;
children = (
FD848B9B284435D7000E298B /* AppSetup.swift */,
C38EF302255B6DBE007E1867 /* OWSAnyTouchGestureRecognizer.h */,
C38EF2F0255B6DBB007E1867 /* OWSAnyTouchGestureRecognizer.m */,
FDCDB8DD2810F73B00352A0C /* Differentiable+Utilities.swift */,
C38EF3DC255B6DF1007E1867 /* DirectionalPanGestureRecognizer.swift */,
C38EF240255B6D67007E1867 /* UIView+OWS.swift */,
@ -3072,14 +3018,8 @@
C33FDB49255A580C00E217F9 /* WeakTimer.swift */,
C33FDBC2255A581700E217F9 /* SSKAsserts.h */,
C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */,
C33FDADC255A580400E217F9 /* NSObject+Casting.h */,
C33FDAAA255A580000E217F9 /* NSObject+Casting.m */,
C33FDBFE255A581C00E217F9 /* NSSet+Functional.h */,
C33FDAC1255A580100E217F9 /* NSSet+Functional.m */,
C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */,
C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */,
C33FDBF8255A581C00E217F9 /* NSArray+OWS.h */,
C33FDB0D255A580800E217F9 /* NSArray+OWS.m */,
C33FDC03255A581D00E217F9 /* ByteParser.h */,
C33FDAE0255A580400E217F9 /* ByteParser.m */,
C38EF3DD255B6DF1007E1867 /* UIAlertController+OWS.swift */,
@ -3527,23 +3467,16 @@
C38EF3F6255B6DF7007E1867 /* OWSTextView.h in Headers */,
C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */,
C38EF32B255B6DBF007E1867 /* OWSFormat.h in Headers */,
C33FDDB8255A582000E217F9 /* NSSet+Functional.h in Headers */,
C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */,
C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */,
C38EF243255B6D67007E1867 /* UIViewController+OWS.h in Headers */,
C38EF35D255B6DCC007E1867 /* OWSNavigationController.h in Headers */,
C38EF249255B6D67007E1867 /* UIColor+OWS.h in Headers */,
C38EF274255B6D7A007E1867 /* OWSResaveCollectionDBMigration.h in Headers */,
C38EF277255B6D7A007E1867 /* OWSDatabaseMigration.h in Headers */,
C38EF3F5255B6DF7007E1867 /* OWSTextField.h in Headers */,
C38EF275255B6D7A007E1867 /* OWSDatabaseMigrationRunner.h in Headers */,
C38EF366255B6DCC007E1867 /* ScreenLockViewController.h in Headers */,
C33FDDD3255A582000E217F9 /* OWSQueues.h in Headers */,
C33FDC96255A582000E217F9 /* NSObject+Casting.h in Headers */,
C33FDDB3255A582000E217F9 /* OWSError.h in Headers */,
C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */,
C37F5396255B95BD002AEA92 /* OWSAnyTouchGestureRecognizer.h in Headers */,
C33FDDB2255A582000E217F9 /* NSArray+OWS.h in Headers */,
C38EF367255B6DCC007E1867 /* OWSTableViewController.h in Headers */,
C38EF246255B6D67007E1867 /* UIFont+OWS.h in Headers */,
C33FD9AF255A548A00E217F9 /* SignalUtilitiesKit.h in Headers */,
@ -3564,7 +3497,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
C32C5FAA256DFED9003C73A2 /* NSArray+Functional.h in Headers */,
C3D9E3FA25676BCE0040E4F3 /* TSYapDatabaseObject.h in Headers */,
C3D9E3A4256763DE0040E4F3 /* AppContext.h in Headers */,
C3D9E38A256760390040E4F3 /* OWSFileSystem.h in Headers */,
@ -4286,14 +4218,11 @@
C38EF3FD255B6DF7007E1867 /* OWSTextView.m in Sources */,
C38EF3C6255B6DE7007E1867 /* ImageEditorModel.swift in Sources */,
C38EF3C3255B6DE7007E1867 /* ImageEditorTextItem.swift in Sources */,
FD28A4F227E990E800FF65E7 /* BlockingManagerRemovalMigration.swift in Sources */,
C33FDC7D255A582000E217F9 /* OWSDispatch.m in Sources */,
C38EF247255B6D67007E1867 /* NSAttributedString+OWS.m in Sources */,
C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */,
C38EF3C5255B6DE7007E1867 /* OWSViewController+ImageEditor.swift in Sources */,
C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */,
C38EF273255B6D7A007E1867 /* OWSDatabaseMigrationRunner.m in Sources */,
C38EF31A255B6DBF007E1867 /* OWSAnyTouchGestureRecognizer.m in Sources */,
C38EF385255B6DD2007E1867 /* AttachmentTextToolbar.swift in Sources */,
C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */,
C38EF389255B6DD2007E1867 /* AttachmentTextView.swift in Sources */,
@ -4311,19 +4240,16 @@
C33FDCD1255A582000E217F9 /* FunctionalUtil.m in Sources */,
C38EF402255B6DF7007E1867 /* CommonStrings.swift in Sources */,
C38EF3C1255B6DE7007E1867 /* ImageEditorBrushViewController.swift in Sources */,
C33FDCC7255A582000E217F9 /* NSArray+OWS.m in Sources */,
C33FDD32255A582000E217F9 /* OWSOperation.m in Sources */,
C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */,
C3D90A7A25773A93002C9DF5 /* Configuration.swift in Sources */,
C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */,
C33FDC64255A582000E217F9 /* NSObject+Casting.m in Sources */,
C38EF388255B6DD2007E1867 /* AttachmentApprovalViewController.swift in Sources */,
C38EF2A7255B6D93007E1867 /* ProfilePictureView.swift in Sources */,
C38EF22C255B6D5D007E1867 /* OWSVideoPlayer.swift in Sources */,
C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */,
C38EF407255B6DF7007E1867 /* Toast.swift in Sources */,
C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */,
FD3C907327E8387300CD579F /* OpenGroupServerIdLookupMigration.swift in Sources */,
C38EF32A255B6DBF007E1867 /* UIUtil.m in Sources */,
C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */,
C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */,
@ -4335,16 +4261,13 @@
C38EF3BA255B6DE7007E1867 /* ImageEditorItem.swift in Sources */,
C38EF3F7255B6DF7007E1867 /* OWSNavigationBar.swift in Sources */,
C38EF248255B6D67007E1867 /* UIViewController+OWS.m in Sources */,
C38EF272255B6D7A007E1867 /* OWSResaveCollectionDBMigration.m in Sources */,
C33FDD3A255A582000E217F9 /* Notification+Loki.swift in Sources */,
FD705A98278E9F4D00F16121 /* UIColor+Extensions.swift in Sources */,
C38EF276255B6D7A007E1867 /* OWSDatabaseMigration.m in Sources */,
C38EF370255B6DCC007E1867 /* OWSNavigationController.m in Sources */,
C38EF24E255B6D67007E1867 /* Collection+OWS.swift in Sources */,
C33FDCFA255A582000E217F9 /* SignalIOSProto.swift in Sources */,
C38EF24D255B6D67007E1867 /* UIView+OWS.swift in Sources */,
C38EF38B255B6DD2007E1867 /* AttachmentPrepViewController.swift in Sources */,
C33FDC7B255A582000E217F9 /* NSSet+Functional.m in Sources */,
C38EF405255B6DF7007E1867 /* OWSButton.swift in Sources */,
C38EF3C4255B6DE7007E1867 /* ImageEditorContents.swift in Sources */,
C38EF3BC255B6DE7007E1867 /* ImageEditorPanGestureRecognizer.swift in Sources */,
@ -4370,11 +4293,9 @@
FDCDB8DE2810F73B00352A0C /* Differentiable+Utilities.swift in Sources */,
C38EF3F9255B6DF7007E1867 /* OWSLayerView.swift in Sources */,
C33FDD03255A582000E217F9 /* WeakTimer.swift in Sources */,
B8B3204E258C15C80020074B /* ContactsMigration.swift in Sources */,
C38EF3B9255B6DE7007E1867 /* ImageEditorPinchGestureRecognizer.swift in Sources */,
C33FDC98255A582000E217F9 /* SwiftSingletons.swift in Sources */,
C33FDC27255A581F00E217F9 /* YapDatabase+Promise.swift in Sources */,
FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */,
C38EF3B8255B6DE7007E1867 /* ImageEditorTextViewController.swift in Sources */,
C38EF40B255B6DF7007E1867 /* TappableStackView.swift in Sources */,
C38EF31D255B6DBF007E1867 /* UIImage+OWS.swift in Sources */,
@ -4460,7 +4381,6 @@
FD705A94278D052B00F16121 /* UITableView+ReusableView.swift in Sources */,
FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */,
C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */,
C32C5FA1256DFED5003C73A2 /* NSArray+Functional.m in Sources */,
C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */,
FD17D7BD27F51F6900122BE0 /* GRDB+Notifications.swift in Sources */,
FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */,

View File

@ -1325,6 +1325,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
}
func conversationSearchController(_ conversationSearchController: ConversationSearchController, didUpdateSearchResults results: [Int64]?, searchText: String?) {
viewModel.lastSearchedText = searchText
tableView.reloadRows(at: tableView.indexPathsForVisibleRows ?? [], with: UITableView.RowAnimation.none)
}

View File

@ -65,10 +65,6 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
columns: Interaction.Columns
.allCases
.filter { $0 != .wasRead }
),
PagedData.ObservedChanges(
table: ThreadTypingIndicator.self,
columns: ThreadTypingIndicator.Columns.allCases
)
],
filterSQL: MessageViewModel.filterSQL(threadId: threadId),
@ -90,6 +86,19 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
joinToPagedType: MessageViewModel.AttachmentInteractionInfo.joinToViewModelQuerySQL,
groupPagedType: MessageViewModel.AttachmentInteractionInfo.groupViewModelQuerySQL,
associateData: MessageViewModel.AttachmentInteractionInfo.createAssociateDataClosure()
),
AssociatedRecord<MessageViewModel.TypingIndicatorInfo, MessageViewModel>(
trackedAgainst: ThreadTypingIndicator.self,
observedChanges: [
PagedData.ObservedChanges(
table: ThreadTypingIndicator.self,
events: [.insert, .delete],
columns: []
)
],
dataQuery: MessageViewModel.TypingIndicatorInfo.baseQuery,
joinToPagedType: MessageViewModel.TypingIndicatorInfo.joinToViewModelQuerySQL,
associateData: MessageViewModel.TypingIndicatorInfo.createAssociateDataClosure()
)
],
onChangeUnsorted: { [weak self] updatedData, updatedPageInfo in
@ -140,6 +149,16 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
/// This value is the current state of the view
public private(set) var threadData: SessionThreadViewModel
/// This is all the data the screen needs to populate itself, please see the following link for tips to help optimise
/// performance https://github.com/groue/GRDB.swift#valueobservation-performance
///
/// **Note:** The 'trackingConstantRegion' is optimised in such a way that the request needs to be static
/// otherwise there may be situations where it doesn't get updates, this means we can't have conditional queries
///
/// **Note:** This observation will be triggered twice immediately (and be de-duped by the `removeDuplicates`)
/// this is due to the behaviour of `ValueConcurrentObserver.asyncStartObservation` which triggers it's own
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
public lazy var observableThreadData = ValueObservation
.trackingConstantRegion { [threadId = self.threadId] db -> SessionThreadViewModel? in
let userPublicKey: String = getUserHexEncodedPublicKey(db)
@ -161,7 +180,9 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
public var onInteractionChange: (([SectionModel]) -> ())?
private func process(data: [MessageViewModel], for pageInfo: PagedData.PageInfo) -> [SectionModel] {
let typingIndicator: MessageViewModel? = data.first(where: { $0.isTypingIndicator })
let sortedData: [MessageViewModel] = data
.filter { !$0.isTypingIndicator }
.sorted { lhs, rhs -> Bool in lhs.timestampMs < rhs.timestampMs }
// We load messages from newest to oldest so having a pageOffset larger than zero means
@ -186,6 +207,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
)
)
}
.appending(typingIndicator)
)
],
(!data.isEmpty && pageInfo.pageOffset > 0 ?

View File

@ -9,7 +9,6 @@
#import "UIView+OWS.h"
#import <Curve25519Kit/Curve25519.h>
#import <SignalCoreKit/NSDate+OWS.h>
#import <SessionMessagingKit/Environment.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SessionMessagingKit/OWSPrimaryStorage.h>

View File

@ -37,6 +37,17 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
return result
}()
private lazy var loadingConversationsLabel: UILabel = {
let result: UILabel = UILabel()
result.font = UIFont.systemFont(ofSize: Values.smallFontSize)
result.text = "LOADING_CONVERSATIONS".localized()
result.textColor = Colors.text
result.textAlignment = .center
result.numberOfLines = 0
return result
}()
private lazy var tableView: UITableView = {
let result = UITableView()
@ -128,6 +139,13 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
seedReminderView.pin(.trailing, to: .trailing, of: view)
}
// Loading conversations label
view.addSubview(loadingConversationsLabel)
loadingConversationsLabel.pin(.top, to: .top, of: view, withInset: Values.veryLargeSpacing)
loadingConversationsLabel.pin(.leading, to: .leading, of: view, withInset: 50)
loadingConversationsLabel.pin(.trailing, to: .trailing, of: view, withInset: -50)
// Table view
view.addSubview(tableView)
tableView.pin(.leading, to: .leading, of: view)
@ -218,11 +236,9 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
// Start observing for data changes
dataChangeObservable = GRDBStorage.shared.start(
viewModel.observableViewData,
onError: { error in
print("Update error!!!!")
},
onError: { _ in },
onChange: { [weak self] viewData in
// The defaul scheduler emits changes on the main thread
// The default scheduler emits changes on the main thread
self?.handleUpdates(viewData)
}
)
@ -237,6 +253,9 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
return
}
// Hide the 'loading conversations' label (now that we have received conversation data)
loadingConversationsLabel.isHidden = true
// Show the empty state if there is no data
emptyStateView.isHidden = (
!updatedViewData.isEmpty &&

View File

@ -17,43 +17,68 @@ public class HomeViewModel {
/// This is all the data the screen needs to populate itself, please see the following link for tips to help optimise
/// performance https://github.com/groue/GRDB.swift#valueobservation-performance
///
/// **Note:** The 'trackingConstantRegion' is optimised in such a way that the request needs to be static
/// otherwise there may be situations where it doesn't get updates, this means we can't have conditional queries
/// **Note:** This observation will be triggered twice immediately (and be de-duped by the `removeDuplicates`)
/// this is due to the behaviour of `ValueConcurrentObserver.asyncStartObservation` which triggers it's own
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
public lazy var observableViewData = ValueObservation
.trackingConstantRegion { db -> [ArraySection<Section, SessionThreadViewModel>] in
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let unreadMessageRequestCount: Int = try SessionThread
.filter(SessionThread.isMessageRequest(userPublicKey: userPublicKey))
.joining(optional: SessionThread.contact)
.joining(
required: SessionThread.interactions
.filter(Interaction.Columns.wasRead == false)
)
.group(SessionThread.Columns.id)
.fetchCount(db)
let finalUnreadMessageRequestCount: Int = (db[.hasHiddenMessageRequests] ? 0 : unreadMessageRequestCount)
return [
ArraySection(
model: .messageRequests,
elements: [
// If there are no unread message requests then hide the message request banner
(finalUnreadMessageRequestCount == 0 ?
nil :
SessionThreadViewModel(
unreadCount: UInt(finalUnreadMessageRequestCount)
)
)
].compactMap { $0 }
.tracking(
regions: [
// We explicitly define the regions we want to track as the automatic detection
// seems to include a bunch of columns we will fetch but probably don't need to
// track changes for
SessionThread.select(
.id,
.shouldBeVisible,
.isPinned,
.mutedUntilTimestamp,
.onlyNotifyForMentions
),
ArraySection(
model: .threads,
elements: try SessionThreadViewModel
.homeQuery(userPublicKey: userPublicKey)
.fetchAll(db)
)
]
}
Setting.filter(id: Setting.BoolKey.hasHiddenMessageRequests.rawValue),
Contact.select(.isBlocked, .isApproved), // 'isApproved' for message requests
Profile.select(.name, .nickname, .profilePictureFileName),
ClosedGroup.select(.name),
OpenGroup.select(.name, .imageData),
GroupMember.select(.groupId),
Interaction.select(
.body,
.wasRead
),
Attachment.select(.state),
RecipientState.select(.state),
ThreadTypingIndicator.select(.threadId)
],
fetch: { db -> [ArraySection<Section, SessionThreadViewModel>] in
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let unreadMessageRequestCount: Int = try SessionThread
.unreadMessageRequestsCountQuery(userPublicKey: userPublicKey)
.fetchOne(db)
.defaulting(to: 0)
let finalUnreadMessageRequestCount: Int = (db[.hasHiddenMessageRequests] ? 0 : unreadMessageRequestCount)
let threads: [SessionThreadViewModel] = try SessionThreadViewModel
.homeQuery(userPublicKey: userPublicKey)
.fetchAll(db)
return [
ArraySection(
model: .messageRequests,
elements: [
// If there are no unread message requests then hide the message request banner
(finalUnreadMessageRequestCount == 0 ?
nil :
SessionThreadViewModel(
unreadCount: UInt(finalUnreadMessageRequestCount)
)
)
].compactMap { $0 }
),
ArraySection(
model: .threads,
elements: threads
)
]
}
)
.removeDuplicates()
// MARK: - Functions

View File

@ -14,6 +14,11 @@ public class MessageRequestsViewModel {
///
/// **Note:** The 'trackingConstantRegion' is optimised in such a way that the request needs to be static
/// otherwise there may be situations where it doesn't get updates, this means we can't have conditional queries
///
/// **Note:** This observation will be triggered twice immediately (and be de-duped by the `removeDuplicates`)
/// this is due to the behaviour of `ValueConcurrentObserver.asyncStartObservation` which triggers it's own
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
public lazy var observableViewData = ValueObservation
.trackingConstantRegion { db -> [SessionThreadViewModel] in
let userPublicKey: String = getUserHexEncodedPublicKey(db)

View File

@ -5,6 +5,7 @@
import Foundation
import Photos
import PromiseKit
import SessionUIKit
protocol ImagePickerGridControllerDelegate: AnyObject {
func imagePickerDidCompleteSelection(_ imagePicker: ImagePickerGridController)
@ -46,6 +47,8 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = Colors.navigationBarBackground
library.add(delegate: self)
@ -59,7 +62,6 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
// ensure images at the end of the list can be scrolled above the bottom buttons
let bottomButtonInset = -1 * SendMediaNavigationController.bottomButtonsCenterOffset + SendMediaNavigationController.bottomButtonWidth / 2 + 16
collectionView.contentInset.bottom = bottomButtonInset + 16
view.backgroundColor = .white
// The PhotoCaptureVC needs a shadow behind it's cancel button, so we use a custom icon.
// This VC has a visible navbar so doesn't need the shadow, but because the user can
@ -69,7 +71,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
let cancelImage = UIImage(imageLiteralResourceName: "X")
let cancelButton = UIBarButtonItem(image: cancelImage, style: .plain, target: self, action: #selector(didPressCancel))
cancelButton.tintColor = .black
cancelButton.tintColor = Colors.text
navigationItem.leftBarButtonItem = cancelButton
let titleView = TitleView()
@ -86,7 +88,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
navigationItem.titleView = titleView
self.titleView = titleView
collectionView.backgroundColor = .white
collectionView.backgroundColor = Colors.navigationBarBackground
let selectionPanGesture = DirectionalPanGestureRecognizer(direction: [.horizontal], target: self, action: #selector(didPanSelection))
selectionPanGesture.delegate = self
@ -187,16 +189,15 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Loki: Set navigation bar background color
let navigationBar = navigationController!.navigationBar
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = false
navigationBar.barTintColor = .white
(navigationBar as! OWSNavigationBar).respectsTheme = false
navigationBar.backgroundColor = .white
let backgroundImage = UIImage(color: .white)
navigationBar.setBackgroundImage(backgroundImage, for: .default)
let backgroundImage: UIImage = UIImage(color: Colors.navigationBarBackground)
self.navigationItem.title = nil
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.barTintColor = Colors.navigationBarBackground
(self.navigationController?.navigationBar as? OWSNavigationBar)?.respectsTheme = true
self.navigationController?.navigationBar.backgroundColor = Colors.navigationBarBackground
self.navigationController?.navigationBar.setBackgroundImage(backgroundImage, for: .default)
// Determine the size of the thumbnails to request
let scale = UIScreen.main.scale
@ -605,10 +606,10 @@ class TitleView: UIView {
addSubview(stackView)
stackView.autoPinEdgesToSuperviewEdges()
label.textColor = .black
label.textColor = Colors.text
label.font = .boldSystemFont(ofSize: Values.mediumFontSize)
iconView.tintColor = .black
iconView.tintColor = Colors.text
iconView.image = UIImage(named: "navbar_disclosure_down")?.withRenderingMode(.alwaysTemplate)
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(titleTapped)))

View File

@ -309,6 +309,11 @@ public class MediaGalleryViewModel {
///
/// **Note:** The 'trackingConstantRegion' is optimised in such a way that the request needs to be static
/// otherwise there may be situations where it doesn't get updates, this means we can't have conditional queries
///
/// **Note:** This observation will be triggered twice immediately (and be de-duped by the `removeDuplicates`)
/// this is due to the behaviour of `ValueConcurrentObserver.asyncStartObservation` which triggers it's own
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
public typealias AlbumObservation = ValueObservation<ValueReducers.RemoveDuplicates<ValueReducers.Fetch<[Item]>>>
public lazy var observableAlbumData: AlbumObservation = buildAlbumObservation(for: nil)

View File

@ -282,8 +282,6 @@ extension SendMediaNavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if viewController == captureViewController {
setNavBarBackgroundColor(to: .black)
} else if viewController == mediaLibraryViewController {
setNavBarBackgroundColor(to: .white)
} else {
setNavBarBackgroundColor(to: Colors.navigationBarBackground)
}
@ -311,8 +309,6 @@ extension SendMediaNavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if viewController == captureViewController {
setNavBarBackgroundColor(to: .black)
} else if viewController == mediaLibraryViewController {
setNavBarBackgroundColor(to: .white)
} else {
setNavBarBackgroundColor(to: Colors.navigationBarBackground)
}

View File

@ -18,9 +18,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
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()
}()
lazy var poller: Poller = Poller()
// MARK: - Lifecycle
@ -67,6 +65,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
migrationsCompletion: { [weak self] successful, needsConfigSync in
guard let strongSelf = self else { return }
guard successful else {
self?.showFailedMigrationAlert()
return
}
@ -211,6 +210,28 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// MARK: - App Readiness
private func showFailedMigrationAlert() {
let alert = UIAlertController(
title: "Session",
message: [
"DATABASE_MIGRATION_FAILED".localized(),
"modal_share_logs_explanation".localized()
].joined(separator: "\n\n"),
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "modal_share_logs_title".localized(), style: .default) { _ in
ShareLogsModal.shareLogs(from: alert) { [weak self] in
self?.showFailedMigrationAlert()
}
})
alert.addAction(UIAlertAction(title: "Close", style: .destructive) { _ in
DDLog.flushLog()
exit(0)
})
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
/// The user must unlock the device once after reboot before the database encryption key can be accessed.
private func verifyDBKeysAvailableBeforeBackgroundLaunch() {
guard UIApplication.shared.applicationState == .background else { return }

View File

@ -5,7 +5,6 @@
#import "MainAppContext.h"
#import "Session-Swift.h"
#import <SignalCoreKit/Threading.h>
#import <SessionMessagingKit/Environment.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -11,8 +11,5 @@
#import <SignalCoreKit/NSObject+OWS.h>
#import <SignalCoreKit/OWSAsserts.h>
#import <SignalUtilitiesKit/SSKAsserts.h>
#import <SessionUtilitiesKit/NSArray+Functional.h>
#import <SignalUtilitiesKit/NSSet+Functional.h>
#import <SignalUtilitiesKit/NSObject+Casting.h>
#import <SessionUIKit/SessionUIKit.h>
#endif

View File

@ -10,11 +10,9 @@
#import "AvatarViewHelper.h"
#import "AVAudioSession+OWS.h"
#import "NotificationSettingsViewController.h"
#import "OWSAnyTouchGestureRecognizer.h"
#import "OWSAudioPlayer.h"
#import "OWSBezierPathView.h"
#import "OWSConversationSettingsViewController.h"
#import "OWSDatabaseMigration.h"
#import "OWSMessageTimerView.h"
#import "OWSNavigationController.h"
#import "OWSProgressView.h"
@ -32,7 +30,6 @@
#import <SignalCoreKit/OWSAsserts.h>
#import <SignalCoreKit/OWSLogs.h>
#import <SignalCoreKit/Threading.h>
#import <SessionMessagingKit/Environment.h>
#import <SessionMessagingKit/OWSAudioPlayer.h>
#import <SignalUtilitiesKit/OWSFormat.h>
#import <SessionMessagingKit/OWSPreferences.h>

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Fehler";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -631,3 +631,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Fallo";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "خطاء";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Erreur";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Galat";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Errore";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "エラー";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Błąd";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Erro";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Ошибка";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -622,3 +622,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "Error";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -621,3 +621,5 @@
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
"ALERT_ERROR_TITLE" = "错误";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database";

View File

@ -6,7 +6,6 @@
#import "Session-Swift.h"
#import <SignalCoreKit/NSString+OWS.h>
#import <SessionMessagingKit/Environment.h>
#import <SessionMessagingKit/OWSPreferences.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>

View File

@ -55,17 +55,24 @@ final class ShareLogsModal : Modal {
contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing)
}
// MARK: Interaction
// MARK: - Interaction
@objc private func shareLogs() {
ShareLogsModal.shareLogs(from: self)
}
public static func shareLogs(from viewController: UIViewController, onShareComplete: (() -> ())? = nil) {
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
OWSLogger.info("[Version] iOS \(UIDevice.current.systemVersion) \(version)")
DDLog.flushLog()
let logFilePaths = AppEnvironment.shared.fileLogger.logFileManager.sortedLogFilePaths
if let latestLogFilePath = logFilePaths.first {
let latestLogFileURL = URL(fileURLWithPath: latestLogFilePath)
self.dismiss(animated: true, completion: {
viewController.dismiss(animated: true, completion: {
if let vc = CurrentAppContext().frontmostViewController() {
let shareVC = UIActivityViewController(activityItems: [ latestLogFileURL ], applicationActivities: nil)
shareVC.completionWithItemsHandler = { _, _, _, _ in onShareComplete?() }
if UIDevice.current.isIPad {
shareVC.excludedActivityTypes = []
shareVC.popoverPresentationController?.permittedArrowDirections = []

View File

@ -5,9 +5,10 @@ import GRDB
import SessionUtilitiesKit
enum _001_InitialSetupMigration: Migration {
static let target: TargetMigrations.Identifier = .messagingKit
static let identifier: String = "initialSetup"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
// Define the tokenizer to be used in all the FTS tables

View File

@ -9,9 +9,10 @@ import SessionSnodeKit
/// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration
/// before running the `YDBToGRDBMigration`
enum _002_SetupStandardJobs: Migration {
static let target: TargetMigrations.Identifier = .messagingKit
static let identifier: String = "SetupStandardJobs"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
// Start by adding the jobs that don't have collections (in the jobs like these

View File

@ -10,15 +10,19 @@ import SessionSnodeKit
// Note: Looks like the oldest iOS device we support (min iOS 13.0) has 2Gb of RAM, processing
// ~250k messages and ~1000 threads seems to take up
enum _003_YDBToGRDBMigration: Migration {
static let target: TargetMigrations.Identifier = .messagingKit
static let identifier: String = "YDBToGRDBMigration"
static let minExpectedRunDuration: TimeInterval = 20
static let needsConfigSync: Bool = true
static let minExpectedRunDuration: TimeInterval = 20
static func migrate(_ db: Database) throws {
let targetIdentifier: TargetMigrations.Identifier = .messagingKit
guard let dbConnection: YapDatabaseConnection = SUKLegacy.newDatabaseConnection() else {
SNLog("[Migration Warning] No legacy database, skipping \(target.key(with: self))")
return
}
// MARK: - Read from Legacy Database
// MARK: - Process Contacts, Threads & Interactions
print("RAWR [\(Date().timeIntervalSince1970)] - SessionMessagingKit migration - Start")
var shouldFailMigration: Bool = false
var legacyMigrations: Set<SMKLegacy._DBMigration> = []
var contacts: Set<SMKLegacy._Contact> = []
@ -48,89 +52,24 @@ enum _003_YDBToGRDBMigration: Migration {
var outgoingReadReceiptsTimestampsMs: [String: Set<Int64>] = [:]
var receivedMessageTimestamps: Set<UInt64> = []
// Map the Legacy types for the NSKeyedUnarchiver
NSKeyedUnarchiver.setClass(
SMKLegacy._Thread.self,
forClassName: "TSThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ContactThread.self,
forClassName: "TSContactThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._GroupThread.self,
forClassName: "TSGroupThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._GroupModel.self,
forClassName: "TSGroupModel"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Contact.self,
forClassName: "SNContact"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBInteraction.self,
forClassName: "TSInteraction"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBMessage.self,
forClassName: "TSMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBQuotedMessage.self,
forClassName: "TSQuotedMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBQuotedMessage._DBAttachmentInfo.self,
forClassName: "OWSAttachmentInfo"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBLinkPreview.self,
forClassName: "SessionServiceKit.OWSLinkPreview" // Very old legacy name
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBLinkPreview.self,
forClassName: "SessionMessagingKit.OWSLinkPreview"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBIncomingMessage.self,
forClassName: "TSIncomingMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBOutgoingMessage.self,
forClassName: "TSOutgoingMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBOutgoingMessageRecipientState.self,
forClassName: "TSOutgoingMessageRecipientState"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBInfoMessage.self,
forClassName: "TSInfoMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DisappearingConfigurationUpdateInfoMessage.self,
forClassName: "OWSDisappearingConfigurationUpdateInfoMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Attachment.self,
forClassName: "TSAttachment"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._AttachmentStream.self,
forClassName: "TSAttachmentStream"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._AttachmentPointer.self,
forClassName: "TSAttachmentPointer"
)
var notifyPushServerJobs: Set<SMKLegacy._NotifyPNServerJob> = []
var messageReceiveJobs: Set<SMKLegacy._MessageReceiveJob> = []
var messageSendJobs: Set<SMKLegacy._MessageSendJob> = []
var attachmentUploadJobs: Set<SMKLegacy._AttachmentUploadJob> = []
var attachmentDownloadJobs: Set<SMKLegacy._AttachmentDownloadJob> = []
Storage.read { transaction in
var legacyPreferences: [String: Any] = [:]
// Map the Legacy types for the NSKeyedUnarchivez
self.mapLegacyTypesForNSKeyedUnarchiver()
dbConnection.read { transaction in
// MARK: --Migrations
// Process the migrations (we don't want to bother running the old migrations as it would be
// a waste of time, rather we include the logic from the old migrations in here and make the
// same changes if the migration hasn't already run)
transaction.enumerateRows(inCollection: SMKLegacy.databaseMigrationCollection) { key, _, _, _ in
transaction.enumerateKeys(inCollection: SMKLegacy.databaseMigrationCollection) { key, _ in
guard let legacyMigration: SMKLegacy._DBMigration = SMKLegacy._DBMigration(rawValue: key) else {
SNLog("[Migration Error] Found unknown migration")
shouldFailMigration = true
@ -139,23 +78,28 @@ enum _003_YDBToGRDBMigration: Migration {
legacyMigrations.insert(legacyMigration)
}
GRDBStorage.shared.update(progress: 0.01, for: self, in: target)
// MARK: --Contacts
SNLog("[Migration Info] \(target.key(with: self)) - Processing Contacts")
// Process the Contacts
transaction.enumerateRows(inCollection: SMKLegacy.contactCollection) { _, object, _, _ in
guard let contact = object as? SMKLegacy._Contact else { return }
contacts.insert(contact)
validProfileIds.insert(contact.sessionID)
}
// Process legacy blocked contacts
legacyBlockedSessionIds = Set(transaction.object(
forKey: SMKLegacy.blockedPhoneNumbersKey,
inCollection: SMKLegacy.blockListCollection
) as? [String] ?? [])
print("RAWR [\(Date().timeIntervalSince1970)] - Process threads - Start")
GRDBStorage.shared.update(progress: 0.02, for: self, in: target)
// MARK: --Threads
SNLog("[Migration Info] \(target.key(with: self)) - Processing Threads")
// Process the threads
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.threadCollection) { key, object, _ in
guard let thread: SMKLegacy._Thread = object as? SMKLegacy._Thread else { return }
@ -237,10 +181,12 @@ enum _003_YDBToGRDBMigration: Migration {
openGroupLastDeletionServerId[thread.uniqueId] = transaction.object(forKey: openGroup.id, inCollection: SMKLegacy.openGroupLastDeletionServerIDCollection) as? Int64
}
}
print("RAWR [\(Date().timeIntervalSince1970)] - Process threads - End")
GRDBStorage.shared.update(progress: 0.04, for: self, in: target)
// MARK: --Interactions
SNLog("[Migration Info] \(target.key(with: self)) - Processing Interactions")
// Process interactions
print("RAWR [\(Date().timeIntervalSince1970)] - Process interactions - Start")
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.interactionCollection) { _, object, _ in
guard let interaction: SMKLegacy._DBInteraction = object as? SMKLegacy._DBInteraction else {
SNLog("[Migration Error] Unable to process interaction")
@ -251,10 +197,12 @@ enum _003_YDBToGRDBMigration: Migration {
interactions[interaction.uniqueThreadId] = (interactions[interaction.uniqueThreadId] ?? [])
.appending(interaction)
}
print("RAWR [\(Date().timeIntervalSince1970)] - Process interactions - End")
GRDBStorage.shared.update(progress: 0.19, for: self, in: target)
// MARK: --Attachments
SNLog("[Migration Info] \(target.key(with: self)) - Processing Attachments")
// Process attachments
print("RAWR [\(Date().timeIntervalSince1970)] - Process attachments - Start")
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.attachmentsCollection) { key, object, _ in
guard let attachment: SMKLegacy._Attachment = object as? SMKLegacy._Attachment else {
SNLog("[Migration Error] Unable to process attachment")
@ -264,9 +212,10 @@ enum _003_YDBToGRDBMigration: Migration {
attachments[key] = attachment
}
print("RAWR [\(Date().timeIntervalSince1970)] - Process attachments - End")
GRDBStorage.shared.update(progress: 0.21, for: self, in: target)
// MARK: --Read Receipts
// Process read receipts
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.outgoingReadReceiptManagerCollection) { key, object, _ in
guard let timestampsMs: Set<Int64> = object as? Set<Int64> else { return }
@ -284,6 +233,84 @@ enum _003_YDBToGRDBMigration: Migration {
.defaulting(to: [])
.asSet()
)
// MARK: --Jobs
SNLog("[Migration Info] \(target.key(with: self)) - Processing Jobs")
transaction.enumerateRows(inCollection: SMKLegacy.notifyPushServerJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._NotifyPNServerJob else { return }
notifyPushServerJobs.insert(job)
}
transaction.enumerateRows(inCollection: SMKLegacy.messageReceiveJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._MessageReceiveJob else { return }
messageReceiveJobs.insert(job)
}
transaction.enumerateRows(inCollection: SMKLegacy.messageSendJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._MessageSendJob else { return }
messageSendJobs.insert(job)
}
transaction.enumerateRows(inCollection: SMKLegacy.attachmentUploadJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._AttachmentUploadJob else { return }
attachmentUploadJobs.insert(job)
}
transaction.enumerateRows(inCollection: SMKLegacy.attachmentDownloadJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._AttachmentDownloadJob else { return }
attachmentDownloadJobs.insert(job)
}
GRDBStorage.shared.update(progress: 0.22, for: self, in: target)
// MARK: --Preferences
SNLog("[Migration Info] \(target.key(with: self)) - Processing Preferences")
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.preferencesCollection) { key, object, _ in
legacyPreferences[key] = object
}
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.additionalPreferencesCollection) { key, object, _ in
legacyPreferences[key] = object
}
// Note: The 'int(forKey:inCollection:)' defaults to `0` which is an incorrect value
// for the notification sound so catch it and default
let globalNotificationSoundValue: Int32 = transaction.int(
forKey: SMKLegacy.soundsGlobalNotificationKey,
inCollection: SMKLegacy.soundsStorageNotificationCollection
)
legacyPreferences[SMKLegacy.soundsGlobalNotificationKey] = (globalNotificationSoundValue > 0 ?
Int(globalNotificationSoundValue) :
Preferences.Sound.defaultNotificationSound.rawValue
)
legacyPreferences[SMKLegacy.readReceiptManagerAreReadReceiptsEnabled] = transaction.bool(
forKey: SMKLegacy.readReceiptManagerAreReadReceiptsEnabled,
inCollection: SMKLegacy.readReceiptManagerCollection,
defaultValue: false
)
legacyPreferences[SMKLegacy.typingIndicatorsEnabledKey] = transaction.bool(
forKey: SMKLegacy.typingIndicatorsEnabledKey,
inCollection: SMKLegacy.typingIndicatorsCollection,
defaultValue: false
)
legacyPreferences[SMKLegacy.screenLockIsScreenLockEnabledKey] = transaction.bool(
forKey: SMKLegacy.screenLockIsScreenLockEnabledKey,
inCollection: SMKLegacy.screenLockCollection,
defaultValue: false
)
legacyPreferences[SMKLegacy.screenLockScreenLockTimeoutSecondsKey] = transaction.double(
forKey: SMKLegacy.screenLockScreenLockTimeoutSecondsKey,
inCollection: SMKLegacy.screenLockCollection,
defaultValue: (15 * 60)
)
GRDBStorage.shared.update(progress: 0.23, for: self, in: target)
}
// We can't properly throw within the 'enumerateKeysAndObjects' block so have to throw here
@ -295,10 +322,14 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: - Insert Contacts
SNLog("[Migration Info] \(target.key(with: self)) - Inserting Contacts")
try autoreleasepool {
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db)
// Values for contact progress
let contactStartProgress: CGFloat = 0.23
let progressPerContact: CGFloat = (0.05 / CGFloat(contacts.count))
try contacts.forEach { legacyContact in
try contacts.enumerated().forEach { index, legacyContact in
let isCurrentUser: Bool = (legacyContact.sessionID == currentUserPublicKey)
let contactThreadId: String = SMKLegacy._ContactThread.threadId(from: legacyContact.sessionID)
@ -368,12 +399,25 @@ enum _003_YDBToGRDBMigration: Migration {
hasBeenBlocked: (!isCurrentUser && (legacyContact.hasBeenBlocked || legacyContact.isBlocked))
).insert(db)
}
// Increment the progress for each contact
GRDBStorage.shared.update(
progress: contactStartProgress + (progressPerContact * CGFloat(index + 1)),
for: self,
in: target
)
}
}
// Clear out processed data (give the memory a change to be freed)
contacts = []
legacyBlockedSessionIds = []
contactThreadIds = []
// MARK: - Insert Threads
print("RAWR [\(Date().timeIntervalSince1970)] - Process thread inserts - Start")
SNLog("[Migration Info] \(target.key(with: self)) - Inserting Threads & Interactions")
var legacyInteractionToIdMap: [String: Int64] = [:]
var legacyInteractionIdentifierToIdMap: [String: Int64] = [:]
var legacyInteractionIdentifierToIdFallbackMap: [String: Int64] = [:]
@ -411,6 +455,12 @@ enum _003_YDBToGRDBMigration: Migration {
.joined(separator: "-")
}
// Values for thread progress
var interactionCounter: CGFloat = 0
let allInteractionsCount: Int = interactions.map { $0.value.count }.reduce(0, +)
let threadInteractionsStartProgress: CGFloat = 0.28
let progressPerInteraction: CGFloat = (0.70 / CGFloat(allInteractionsCount))
// Sort by id just so we can make the migration process more determinstic
try legacyThreads.sorted(by: { lhs, rhs in lhs.uniqueId < rhs.uniqueId }).forEach { legacyThread in
guard let threadId: String = legacyThreadIdToIdMap[legacyThread.uniqueId] else {
@ -921,25 +971,22 @@ enum _003_YDBToGRDBMigration: Migration {
attachmentId: attachmentId
).insert(db)
}
}
// Increment the progress for each contact
GRDBStorage.shared.update(
progress: (
threadInteractionsStartProgress +
(progressPerInteraction * (interactionCounter + 1))
),
for: self,
in: target
)
interactionCounter += 1
}
}
}
// Insert a 'ControlMessageProcessRecord' for any remaining 'receivedMessageTimestamp'
// entries as "legacy"
try ControlMessageProcessRecord.generateLegacyProcessRecords(
db,
receivedMessageTimestamps: receivedMessageTimestamps.map { Int64($0) }
)
print("RAWR [\(Date().timeIntervalSince1970)] - Process thread inserts - End")
// Clear out processed data (give the memory a change to be freed)
contacts = []
legacyBlockedSessionIds = []
contactThreadIds = []
legacyThreads = []
disappearingMessagesConfiguration = [:]
@ -957,150 +1004,24 @@ enum _003_YDBToGRDBMigration: Migration {
interactions = [:]
attachments = [:]
// MARK: --Received Message Timestamps
// Insert a 'ControlMessageProcessRecord' for any remaining 'receivedMessageTimestamp'
// entries as "legacy"
try ControlMessageProcessRecord.generateLegacyProcessRecords(
db,
receivedMessageTimestamps: receivedMessageTimestamps.map { Int64($0) }
)
// Clear out processed data (give the memory a change to be freed)
receivedMessageTimestamps = []
// MARK: - Process Legacy Jobs
print("RAWR [\(Date().timeIntervalSince1970)] - Process jobs - Start")
var notifyPushServerJobs: Set<SMKLegacy._NotifyPNServerJob> = []
var messageReceiveJobs: Set<SMKLegacy._MessageReceiveJob> = []
var messageSendJobs: Set<SMKLegacy._MessageSendJob> = []
var attachmentUploadJobs: Set<SMKLegacy._AttachmentUploadJob> = []
var attachmentDownloadJobs: Set<SMKLegacy._AttachmentDownloadJob> = []
// Map the Legacy types for the NSKeyedUnarchiver
NSKeyedUnarchiver.setClass(
SMKLegacy._NotifyPNServerJob.self,
forClassName: "SessionMessagingKit.NotifyPNServerJob"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._NotifyPNServerJob._SnodeMessage.self,
forClassName: "SessionSnodeKit.SnodeMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._MessageSendJob.self,
forClassName: "SessionMessagingKit.SNMessageSendJob"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._MessageReceiveJob.self,
forClassName: "SessionMessagingKit.MessageReceiveJob"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._AttachmentUploadJob.self,
forClassName: "SessionMessagingKit.AttachmentUploadJob"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._AttachmentDownloadJob.self,
forClassName: "SessionMessagingKit.AttachmentDownloadJob"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Message.self,
forClassName: "SNMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._VisibleMessage.self,
forClassName: "SNVisibleMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Quote.self,
forClassName: "SNQuote"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._LinkPreview.self,
forClassName: "SNLinkPreview"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Profile.self,
forClassName: "SNProfile"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._OpenGroupInvitation.self,
forClassName: "SNOpenGroupInvitation"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ControlMessage.self,
forClassName: "SNControlMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ReadReceipt.self,
forClassName: "SNReadReceipt"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._TypingIndicator.self,
forClassName: "SNTypingIndicator"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ClosedGroupControlMessage.self,
forClassName: "SessionMessagingKit.ClosedGroupControlMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ClosedGroupControlMessage._KeyPairWrapper.self,
forClassName: "ClosedGroupControlMessage.SNKeyPairWrapper"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DataExtractionNotification.self,
forClassName: "SessionMessagingKit.DataExtractionNotification"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ExpirationTimerUpdate.self,
forClassName: "SNExpirationTimerUpdate"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ConfigurationMessage.self,
forClassName: "SNConfigurationMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._CMClosedGroup.self,
forClassName: "SNClosedGroup"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._CMContact.self,
forClassName: "SNConfigurationMessage.SNConfigurationMessageContact"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._UnsendRequest.self,
forClassName: "SNUnsendRequest"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._MessageRequestResponse.self,
forClassName: "SNMessageRequestResponse"
)
Storage.read { transaction in
transaction.enumerateRows(inCollection: SMKLegacy.notifyPushServerJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._NotifyPNServerJob else { return }
notifyPushServerJobs.insert(job)
}
transaction.enumerateRows(inCollection: SMKLegacy.messageReceiveJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._MessageReceiveJob else { return }
messageReceiveJobs.insert(job)
}
transaction.enumerateRows(inCollection: SMKLegacy.messageSendJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._MessageSendJob else { return }
messageSendJobs.insert(job)
}
transaction.enumerateRows(inCollection: SMKLegacy.attachmentUploadJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._AttachmentUploadJob else { return }
attachmentUploadJobs.insert(job)
}
transaction.enumerateRows(inCollection: SMKLegacy.attachmentDownloadJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._AttachmentDownloadJob else { return }
attachmentDownloadJobs.insert(job)
}
}
print("RAWR [\(Date().timeIntervalSince1970)] - Process jobs - End")
// MARK: - Insert Jobs
print("RAWR [\(Date().timeIntervalSince1970)] - Process job inserts - Start")
SNLog("[Migration Info] \(target.key(with: self)) - Inserting Jobs")
// MARK: - --notifyPushServer
// MARK: --notifyPushServer
try autoreleasepool {
try notifyPushServerJobs.forEach { legacyJob in
@ -1123,7 +1044,7 @@ enum _003_YDBToGRDBMigration: Migration {
}
}
// MARK: - --messageReceive
// MARK: --messageReceive
try autoreleasepool {
try messageReceiveJobs.forEach { legacyJob in
@ -1172,7 +1093,7 @@ enum _003_YDBToGRDBMigration: Migration {
}
}
// MARK: - --messageSend
// MARK: --messageSend
var messageSendJobLegacyMap: [String: Job] = [:]
@ -1266,7 +1187,7 @@ enum _003_YDBToGRDBMigration: Migration {
}
}
// MARK: - --attachmentUpload
// MARK: --attachmentUpload
try autoreleasepool {
try attachmentUploadJobs.forEach { legacyJob in
@ -1300,7 +1221,7 @@ enum _003_YDBToGRDBMigration: Migration {
}
}
// MARK: - --attachmentDownload
// MARK: --attachmentDownload
try autoreleasepool {
try attachmentDownloadJobs.forEach { legacyJob in
@ -1327,7 +1248,7 @@ enum _003_YDBToGRDBMigration: Migration {
}
}
// MARK: - --sendReadReceipts
// MARK: --sendReadReceipts
try autoreleasepool {
try outgoingReadReceiptsTimestampsMs.forEach { threadId, timestampsMs in
@ -1342,59 +1263,11 @@ enum _003_YDBToGRDBMigration: Migration {
)?.inserted(db)
}
}
GRDBStorage.shared.update(progress: 0.99, for: self, in: target)
print("RAWR [\(Date().timeIntervalSince1970)] - Process job inserts - End")
// MARK: - Preferences
// MARK: - Process Preferences
print("RAWR [\(Date().timeIntervalSince1970)] - Process preferences inserts - Start")
var legacyPreferences: [String: Any] = [:]
Storage.read { transaction in
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.preferencesCollection) { key, object, _ in
legacyPreferences[key] = object
}
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.additionalPreferencesCollection) { key, object, _ in
legacyPreferences[key] = object
}
// Note: The 'int(forKey:inCollection:)' defaults to `0` which is an incorrect value
// for the notification sound so catch it and default
let globalNotificationSoundValue: Int32 = transaction.int(
forKey: SMKLegacy.soundsGlobalNotificationKey,
inCollection: SMKLegacy.soundsStorageNotificationCollection
)
legacyPreferences[SMKLegacy.soundsGlobalNotificationKey] = (globalNotificationSoundValue > 0 ?
Int(globalNotificationSoundValue) :
Preferences.Sound.defaultNotificationSound.rawValue
)
legacyPreferences[SMKLegacy.readReceiptManagerAreReadReceiptsEnabled] = transaction.bool(
forKey: SMKLegacy.readReceiptManagerAreReadReceiptsEnabled,
inCollection: SMKLegacy.readReceiptManagerCollection,
defaultValue: false
)
legacyPreferences[SMKLegacy.typingIndicatorsEnabledKey] = transaction.bool(
forKey: SMKLegacy.typingIndicatorsEnabledKey,
inCollection: SMKLegacy.typingIndicatorsCollection,
defaultValue: false
)
legacyPreferences[SMKLegacy.screenLockIsScreenLockEnabledKey] = transaction.bool(
forKey: SMKLegacy.screenLockIsScreenLockEnabledKey,
inCollection: SMKLegacy.screenLockCollection,
defaultValue: false
)
legacyPreferences[SMKLegacy.screenLockScreenLockTimeoutSecondsKey] = transaction.double(
forKey: SMKLegacy.screenLockScreenLockTimeoutSecondsKey,
inCollection: SMKLegacy.screenLockCollection,
defaultValue: (15 * 60)
)
}
SNLog("[Migration Info] \(target.key(with: self)) - Inserting Preferences")
db[.defaultNotificationSound] = Preferences.Sound(rawValue: legacyPreferences[SMKLegacy.soundsGlobalNotificationKey] as? Int ?? -1)
.defaulting(to: Preferences.Sound.defaultNotificationSound)
@ -1425,10 +1298,6 @@ enum _003_YDBToGRDBMigration: Migration {
db[.hasSavedThread] = (legacyPreferences[SMKLegacy.preferencesKeyHasSavedThreadKey] as? Bool == true)
db[.hasSentAMessage] = (legacyPreferences[SMKLegacy.preferencesKeyHasSentAMessageKey] as? Bool == true)
db[.isReadyForAppExtensions] = CurrentAppContext().appUserDefaults().bool(forKey: SMKLegacy.preferencesKeyIsReadyForAppExtensions)
print("RAWR [\(Date().timeIntervalSince1970)] - Process preferences inserts - End")
print("RAWR Done!!!")
}
// MARK: - Convenience
@ -1582,4 +1451,179 @@ enum _003_YDBToGRDBMigration: Migration {
return legacyAttachmentId
}
private static func mapLegacyTypesForNSKeyedUnarchiver() {
NSKeyedUnarchiver.setClass(
SMKLegacy._Thread.self,
forClassName: "TSThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ContactThread.self,
forClassName: "TSContactThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._GroupThread.self,
forClassName: "TSGroupThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._GroupModel.self,
forClassName: "TSGroupModel"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Contact.self,
forClassName: "SNContact"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBInteraction.self,
forClassName: "TSInteraction"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBMessage.self,
forClassName: "TSMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBQuotedMessage.self,
forClassName: "TSQuotedMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBQuotedMessage._DBAttachmentInfo.self,
forClassName: "OWSAttachmentInfo"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBLinkPreview.self,
forClassName: "SessionServiceKit.OWSLinkPreview" // Very old legacy name
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBLinkPreview.self,
forClassName: "SessionMessagingKit.OWSLinkPreview"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBIncomingMessage.self,
forClassName: "TSIncomingMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBOutgoingMessage.self,
forClassName: "TSOutgoingMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBOutgoingMessageRecipientState.self,
forClassName: "TSOutgoingMessageRecipientState"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DBInfoMessage.self,
forClassName: "TSInfoMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DisappearingConfigurationUpdateInfoMessage.self,
forClassName: "OWSDisappearingConfigurationUpdateInfoMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Attachment.self,
forClassName: "TSAttachment"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._AttachmentStream.self,
forClassName: "TSAttachmentStream"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._AttachmentPointer.self,
forClassName: "TSAttachmentPointer"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._NotifyPNServerJob.self,
forClassName: "SessionMessagingKit.NotifyPNServerJob"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._NotifyPNServerJob._SnodeMessage.self,
forClassName: "SessionSnodeKit.SnodeMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._MessageSendJob.self,
forClassName: "SessionMessagingKit.SNMessageSendJob"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._MessageReceiveJob.self,
forClassName: "SessionMessagingKit.MessageReceiveJob"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._AttachmentUploadJob.self,
forClassName: "SessionMessagingKit.AttachmentUploadJob"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._AttachmentDownloadJob.self,
forClassName: "SessionMessagingKit.AttachmentDownloadJob"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Message.self,
forClassName: "SNMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._VisibleMessage.self,
forClassName: "SNVisibleMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Quote.self,
forClassName: "SNQuote"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._LinkPreview.self,
forClassName: "SNLinkPreview"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Profile.self,
forClassName: "SNProfile"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._OpenGroupInvitation.self,
forClassName: "SNOpenGroupInvitation"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ControlMessage.self,
forClassName: "SNControlMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ReadReceipt.self,
forClassName: "SNReadReceipt"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._TypingIndicator.self,
forClassName: "SNTypingIndicator"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ClosedGroupControlMessage.self,
forClassName: "SessionMessagingKit.ClosedGroupControlMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ClosedGroupControlMessage._KeyPairWrapper.self,
forClassName: "ClosedGroupControlMessage.SNKeyPairWrapper"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._DataExtractionNotification.self,
forClassName: "SessionMessagingKit.DataExtractionNotification"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ExpirationTimerUpdate.self,
forClassName: "SNExpirationTimerUpdate"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ConfigurationMessage.self,
forClassName: "SNConfigurationMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._CMClosedGroup.self,
forClassName: "SNClosedGroup"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._CMContact.self,
forClassName: "SNConfigurationMessage.SNConfigurationMessageContact"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._UnsendRequest.self,
forClassName: "SNUnsendRequest"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._MessageRequestResponse.self,
forClassName: "SNMessageRequestResponse"
)
}
}

View File

@ -122,7 +122,6 @@ public extension RecipientState {
public extension RecipientState {
static func selectInteractionState(tableLiteral: SQL, idColumnLiteral: SQL) -> SQL {
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let recipientState: TypedTableAlias<RecipientState> = TypedTableAlias()
return """
@ -132,7 +131,6 @@ public extension RecipientState {
\(recipientState[.state]),
\(recipientState[.mostRecentFailureText])
FROM \(RecipientState.self)
JOIN \(Interaction.self) ON \(interaction[.id]) = \(recipientState[.interactionId])
WHERE \(SQL("\(recipientState[.state]) != \(RecipientState.State.skipped)")) -- Ignore 'skipped'
ORDER BY
-- If there is a single 'sending' then should be 'sending', otherwise if there is a single

View File

@ -209,22 +209,46 @@ public extension SessionThread {
// MARK: - Convenience
public extension SessionThread {
static func unreadMessageRequestsCountQuery(userPublicKey: String) -> SQLRequest<Int> {
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
let contact: TypedTableAlias<Contact> = TypedTableAlias()
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let unreadInteractionLiteral: SQL = SQL(stringLiteral: "unreadInteraction")
let interactionThreadIdColumnLiteral: SQL = SQL(stringLiteral: Interaction.Columns.threadId.name)
return """
SELECT COUNT(\(thread[.id]))
FROM \(SessionThread.self)
JOIN (
SELECT \(interaction[.threadId])
FROM \(Interaction.self)
WHERE \(interaction[.wasRead]) = false
GROUP BY \(interaction[.threadId])
) AS \(unreadInteractionLiteral) ON \(unreadInteractionLiteral).\(interactionThreadIdColumnLiteral) = \(thread[.id])
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
WHERE (
\(SessionThread.isMessageRequest(userPublicKey: userPublicKey))
)
"""
}
/// This method can be used to filter a thread query to only include messages requests
///
/// **Note:** In order to use this filter you **MUST** have a `joining(required/optional:)` to the
/// `SessionThread.contact` association or it won't work
static func isMessageRequest(userPublicKey: String) -> SQLSpecificExpressible {
let threadAlias: TypedTableAlias<SessionThread> = TypedTableAlias()
let contactAlias: TypedTableAlias<Contact> = TypedTableAlias()
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
let contact: TypedTableAlias<Contact> = TypedTableAlias()
return SQL(
"""
\(threadAlias[.shouldBeVisible]) = true AND
\(SQL("\(threadAlias[.variant]) = \(SessionThread.Variant.contact)")) AND
\(SQL("\(threadAlias[.id]) != \(userPublicKey)")) AND (
\(thread[.shouldBeVisible]) = true AND
\(SQL("\(thread[.variant]) = \(SessionThread.Variant.contact)")) AND
\(SQL("\(thread[.id]) != \(userPublicKey)")) AND (
/* Note: A '!= true' check doesn't work properly so we need to be explicit */
\(contactAlias[.isApproved]) IS NULL OR
\(contactAlias[.isApproved]) = false
\(contact[.isApproved]) IS NULL OR
\(contact[.isApproved]) = false
)
"""
)

View File

@ -14,7 +14,7 @@ public struct ThreadTypingIndicator: Codable, FetchableRecord, PersistableRecord
private static let thread = belongsTo(SessionThread.self, using: threadForeignKey)
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
public enum CodingKeys: String, CodingKey, ColumnExpression {
case threadId
case timestampMs
}

View File

@ -4,7 +4,6 @@ FOUNDATION_EXPORT double SessionMessagingKitVersionNumber;
FOUNDATION_EXPORT const unsigned char SessionMessagingKitVersionString[];
#import <SessionMessagingKit/AppReadiness.h>
#import <SessionMessagingKit/Environment.h>
#import <SessionMessagingKit/NSData+messagePadding.h>
#import <SessionMessagingKit/OWSAudioPlayer.h>
#import <SessionMessagingKit/OWSBackgroundTask.h>

View File

@ -6,9 +6,7 @@ import PromiseKit
import Sodium
import SessionSnodeKit
@objc(LKPoller)
public final class Poller : NSObject {
private let storage = OWSPrimaryStorage.shared()
public final class Poller {
private var isPolling: Atomic<Bool> = Atomic(false)
private var usedSnodes = Set<Snode>()
private var pollCount = 0
@ -27,7 +25,7 @@ public final class Poller : NSObject {
// MARK: - Error
private enum Error : LocalizedError {
private enum Error: LocalizedError {
case pollLimitReached
var localizedDescription: String {
@ -39,7 +37,9 @@ public final class Poller : NSObject {
// MARK: - Public API
@objc public func startIfNeeded() {
public init() {}
public func startIfNeeded() {
guard !isPolling.wrappedValue else { return }
SNLog("Started polling.")
@ -47,7 +47,7 @@ public final class Poller : NSObject {
setUpPolling()
}
@objc public func stop() {
public func stop() {
SNLog("Stopped polling.")
isPolling.mutate { $0 = false }
usedSnodes.removeAll()

View File

@ -7,6 +7,7 @@ import SessionUtilitiesKit
fileprivate typealias ViewModel = MessageViewModel
fileprivate typealias AttachmentInteractionInfo = MessageViewModel.AttachmentInteractionInfo
fileprivate typealias TypingIndicatorInfo = MessageViewModel.TypingIndicatorInfo
public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, Hashable, Identifiable, Differentiable {
public static let threadVariantKey: SQL = SQL(stringLiteral: CodingKeys.threadVariant.stringValue)
@ -384,9 +385,28 @@ public extension MessageViewModel {
}
}
// MARK: - TypingIndicatorInfo
public extension MessageViewModel {
struct TypingIndicatorInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable {
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
public static let threadIdKey: SQL = SQL(stringLiteral: CodingKeys.threadId.stringValue)
public let rowId: Int64
public let threadId: String
// MARK: - Identifiable
public var id: String { threadId }
}
}
// MARK: - Convenience Initialization
public extension MessageViewModel {
public static let genericId: Int64 = -2
public static let typingIndicatorId: Int64 = -2
// Note: This init method is only used system-created cells or empty states
init(isTypingIndicator: Bool = false) {
self.threadVariant = .contact
@ -395,8 +415,9 @@ public extension MessageViewModel {
// Interaction Info
self.rowId = -1
self.id = -1
let targetId: Int64 = (isTypingIndicator ? MessageViewModel.typingIndicatorId : MessageViewModel.genericId)
self.rowId = targetId
self.id = targetId
self.variant = .standardOutgoing
self.timestampMs = Int64.max
self.authorId = ""
@ -473,6 +494,8 @@ extension MessageViewModel {
// MARK: - ConversationVC
// MARK: --MessageViewModel
public extension MessageViewModel {
static func filterSQL(threadId: String) -> SQL {
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
@ -612,6 +635,8 @@ public extension MessageViewModel {
}
}
// MARK: --AttachmentInteractionInfo
public extension MessageViewModel.AttachmentInteractionInfo {
static let baseQuery: ((SQL?) -> AdaptedFetchRequest<SQLRequest<MessageViewModel.AttachmentInteractionInfo>>) = {
return { additionalFilters -> AdaptedFetchRequest<SQLRequest<AttachmentInteractionInfo>> in
@ -697,3 +722,51 @@ public extension MessageViewModel.AttachmentInteractionInfo {
}
}
}
// MARK: --TypingIndicatorInfo
public extension MessageViewModel.TypingIndicatorInfo {
static let baseQuery: ((SQL?) -> SQLRequest<MessageViewModel.TypingIndicatorInfo>) = {
return { additionalFilters -> SQLRequest<TypingIndicatorInfo> in
let threadTypingIndicator: TypedTableAlias<ThreadTypingIndicator> = TypedTableAlias()
let finalFilterSQL: SQL = {
guard let additionalFilters: SQL = additionalFilters else {
return SQL(stringLiteral: "")
}
return """
WHERE \(additionalFilters)
"""
}()
let request: SQLRequest<MessageViewModel.TypingIndicatorInfo> = """
SELECT
\(threadTypingIndicator.alias[Column.rowID]) AS \(MessageViewModel.TypingIndicatorInfo.rowIdKey),
\(threadTypingIndicator[.threadId]) AS \(MessageViewModel.TypingIndicatorInfo.threadIdKey)
FROM \(ThreadTypingIndicator.self)
\(finalFilterSQL)
"""
return request
}
}()
static var joinToViewModelQuerySQL: SQL = {
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let threadTypingIndicator: TypedTableAlias<ThreadTypingIndicator> = TypedTableAlias()
return """
JOIN \(Interaction.self) ON \(interaction[.threadId]) = \(threadTypingIndicator[.threadId])
"""
}()
static func createAssociateDataClosure() -> (DataCache<MessageViewModel.TypingIndicatorInfo>, DataCache<MessageViewModel>) -> DataCache<MessageViewModel> {
return { dataCache, pagedDataCache -> DataCache<MessageViewModel> in
guard !dataCache.data.isEmpty else {
return pagedDataCache.deleting(rowIds: [MessageViewModel.typingIndicatorId])
}
return pagedDataCache
.upserting(MessageViewModel(isTypingIndicator: true))
}
}
}

View File

@ -264,7 +264,6 @@ public extension SessionThreadViewModel {
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let linkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias()
let interactionAttachment: TypedTableAlias<InteractionAttachment> = TypedTableAlias()
let attachment: TypedTableAlias<Attachment> = TypedTableAlias()
let unreadCountTableLiteral: SQL = SQL(stringLiteral: "\(ViewModel.threadUnreadCountString)_table")
let unreadMentionCountTableLiteral: SQL = SQL(stringLiteral: "\(ViewModel.threadUnreadMentionCountString)_table")
@ -274,6 +273,9 @@ public extension SessionThreadViewModel {
let profileNameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.name.name)
let authorProfileLiteral: SQL = SQL(stringLiteral: "authorProfile")
let attachmentIdColumnLiteral: SQL = SQL(stringLiteral: Attachment.Columns.id.name)
let attachmentVariantColumnLiteral: SQL = SQL(stringLiteral: Attachment.Columns.variant.name)
let attachmentContentTypeColumnLiteral: SQL = SQL(stringLiteral: Attachment.Columns.contentType.name)
let attachmentSourceFilenameColumnLiteral: SQL = SQL(stringLiteral: Attachment.Columns.sourceFilename.name)
let firstInteractionAttachmentLiteral: SQL = SQL(stringLiteral: "firstInteractionAttachment")
let interactionAttachmentAttachmentIdColumnLiteral: SQL = SQL(stringLiteral: InteractionAttachment.Columns.attachmentId.name)
let interactionAttachmentInteractionIdColumnLiteral: SQL = SQL(stringLiteral: InteractionAttachment.Columns.interactionId.name)
@ -288,8 +290,9 @@ public extension SessionThreadViewModel {
///
/// Explicitly set default values for the fields ignored for search results
let numColumnsBeforeProfiles: Int = 11
let numColumnsBetweenProfilesAndAttachmentInfo: Int = 10
let numColumnsBetweenProfilesAndAttachmentInfo: Int = 10 // The attachment info columns will be combined
// TODO: Some testing around the subqueries in the joins to see if they impact performance ('Simulator1' device takes ~125ms to complete this query)
let request: SQLRequest<ViewModel> = """
SELECT
\(thread[.id]) AS \(ViewModel.threadIdKey),
@ -322,7 +325,10 @@ public extension SessionThreadViewModel {
-- Default to 'sending' assuming non-processed interaction when null
IFNULL(\(interactionStateTableLiteral).\(interactionStateStateColumnLiteral), \(SQL("\(RecipientState.State.sending)"))) AS \(interactionStateTableLiteral),
(\(linkPreview[.url]) IS NOT NULL) AS \(ViewModel.interactionIsOpenGroupInvitationKey),
\(ViewModel.interactionAttachmentDescriptionInfoKey).*,
\(ViewModel.interactionAttachmentDescriptionInfoKey).\(attachmentIdColumnLiteral),
\(ViewModel.interactionAttachmentDescriptionInfoKey).\(attachmentVariantColumnLiteral),
\(ViewModel.interactionAttachmentDescriptionInfoKey).\(attachmentContentTypeColumnLiteral),
\(ViewModel.interactionAttachmentDescriptionInfoKey).\(attachmentSourceFilenameColumnLiteral),
COUNT(\(interactionAttachment[.interactionId])) AS \(ViewModel.interactionAttachmentCountKey),
\(interaction[.authorId]),
@ -406,14 +412,7 @@ public extension SessionThreadViewModel {
\(firstInteractionAttachmentLiteral).\(interactionAttachmentAlbumIndexColumnLiteral) = 0 AND
\(firstInteractionAttachmentLiteral).\(interactionAttachmentInteractionIdColumnLiteral) = \(interaction[.id])
)
LEFT JOIN (
SELECT
\(attachment[.id]),
\(attachment[.variant]),
\(attachment[.contentType]),
\(attachment[.sourceFilename])
FROM \(Attachment.self)
) AS \(ViewModel.interactionAttachmentDescriptionInfoKey) ON \(ViewModel.interactionAttachmentDescriptionInfoKey).\(attachmentIdColumnLiteral) = \(firstInteractionAttachmentLiteral).\(interactionAttachmentAttachmentIdColumnLiteral)
LEFT JOIN \(Attachment.self) AS \(ViewModel.interactionAttachmentDescriptionInfoKey) ON \(ViewModel.interactionAttachmentDescriptionInfoKey).\(attachmentIdColumnLiteral) = \(firstInteractionAttachmentLiteral).\(interactionAttachmentAttachmentIdColumnLiteral)
LEFT JOIN (
\(RecipientState.selectInteractionState(
tableLiteral: interactionStateTableLiteral,
@ -506,7 +505,6 @@ public extension SessionThreadViewModel {
static func conversationQuery(threadId: String, userPublicKey: String) -> AdaptedFetchRequest<SQLRequest<SessionThreadViewModel>> {
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
let contact: TypedTableAlias<Contact> = TypedTableAlias()
let typingIndicator: TypedTableAlias<ThreadTypingIndicator> = TypedTableAlias() // TODO: Remove this (not needed here - tracked via the messages)
let closedGroup: TypedTableAlias<ClosedGroup> = TypedTableAlias()
let groupMember: TypedTableAlias<GroupMember> = TypedTableAlias()
let openGroup: TypedTableAlias<OpenGroup> = TypedTableAlias()
@ -526,7 +524,7 @@ public extension SessionThreadViewModel {
/// parse and might throw
///
/// Explicitly set default values for the fields ignored for search results
let numColumnsBeforeProfiles: Int = 16
let numColumnsBeforeProfiles: Int = 15
let request: SQLRequest<ViewModel> = """
SELECT
\(thread[.id]) AS \(ViewModel.threadIdKey),
@ -557,7 +555,6 @@ public extension SessionThreadViewModel {
\(thread[.onlyNotifyForMentions]) AS \(ViewModel.threadOnlyNotifyForMentionsKey),
\(thread[.messageDraft]) AS \(ViewModel.threadMessageDraftKey),
(\(typingIndicator[.threadId]) IS NOT NULL) AS \(ViewModel.threadContactIsTypingKey),
\(unreadCountTableLiteral).\(ViewModel.threadUnreadCountKey) AS \(ViewModel.threadUnreadCountKey),
\(unreadMentionCountTableLiteral).\(ViewModel.threadUnreadMentionCountKey) AS \(ViewModel.threadUnreadMentionCountKey),
\(firstUnreadInteractionTableLiteral).\(interactionIdLiteral) AS \(ViewModel.threadFirstUnreadInteractionIdKey),
@ -578,7 +575,6 @@ public extension SessionThreadViewModel {
FROM \(SessionThread.self)
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
LEFT JOIN \(ThreadTypingIndicator.self) ON \(typingIndicator[.threadId]) = \(thread[.id])
LEFT JOIN (
SELECT
\(interaction[.id]),
@ -870,7 +866,7 @@ public extension SessionThreadViewModel {
\(ViewModel.closedGroupProfileBackFallbackKey).\(profileIdColumnLiteral) = \(userPublicKey)
)
ORDER BY \(Column.rank)
ORDER BY \(Column.rank), \(interaction[.timestampMs].desc)
"""
return request.adapted { db in

View File

@ -3,7 +3,6 @@
//
#import "OWSWindowManager.h"
#import "Environment.h"
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
#import <SessionUtilitiesKit/SessionUtilitiesKit.h>

View File

@ -9,12 +9,10 @@
#import <SignalCoreKit/NSObject+OWS.h>
#import <SignalCoreKit/OWSAsserts.h>
#import <SignalCoreKit/OWSLogs.h>
#import <SessionMessagingKit/Environment.h>
#import <SessionMessagingKit/OWSPreferences.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/UIFont+OWS.h>
#import <SessionUtilitiesKit/UIView+OWS.h>
#import <SignalUtilitiesKit/VersionMigrations.h>
#import <SessionUtilitiesKit/AppContext.h>
#import <SessionMessagingKit/AppReadiness.h>
#import <SignalUtilitiesKit/AppVersion.h>

View File

@ -15,6 +15,11 @@ public class ThreadPickerViewModel {
///
/// **Note:** The 'trackingConstantRegion' is optimised in such a way that the request needs to be static
/// otherwise there may be situations where it doesn't get updates, this means we can't have conditional queries
///
/// **Note:** This observation will be triggered twice immediately (and be de-duped by the `removeDuplicates`)
/// this is due to the behaviour of `ValueConcurrentObserver.asyncStartObservation` which triggers it's own
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
public lazy var observableViewData = ValueObservation
.trackingConstantRegion { db -> [SessionThreadViewModel] in
let userPublicKey: String = getUserHexEncodedPublicKey(db)

View File

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

View File

@ -7,9 +7,10 @@ import SessionUtilitiesKit
/// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration
/// before running the `YDBToGRDBMigration`
enum _002_SetupStandardJobs: Migration {
static let target: TargetMigrations.Identifier = .snodeKit
static let identifier: String = "SetupStandardJobs"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
try autoreleasepool {

View File

@ -5,17 +5,29 @@ import GRDB
import SessionUtilitiesKit
enum _003_YDBToGRDBMigration: Migration {
static let target: TargetMigrations.Identifier = .snodeKit
static let identifier: String = "YDBToGRDBMigration"
static let minExpectedRunDuration: TimeInterval = 0.2
static let needsConfigSync: Bool = false
/// This migration can take a while if it's a very large database or there are lots of closed groups (want this to account
/// for about 10% of the progress bar so we intentionally have a higher `minExpectedRunDuration` so show more
/// progress during the migration)
static let minExpectedRunDuration: TimeInterval = 2.0
static func migrate(_ db: Database) throws {
// MARK: - OnionRequestPath, Snode Pool & Swarm
guard let dbConnection: YapDatabaseConnection = SUKLegacy.newDatabaseConnection() else {
SNLog("[Migration Warning] No legacy database, skipping \(target.key(with: self))")
return
}
// MARK: - Read from Legacy Database
// Note: Want to exclude the Snode's we already added from the 'onionRequestPathResult'
var snodeResult: Set<SSKLegacy.Snode> = []
var snodeSetResult: [String: Set<SSKLegacy.Snode>] = [:]
var lastSnodePoolRefreshDate: Date? = nil
var lastMessageResults: [String: (hash: String, json: JSON)] = [:]
var receivedMessageResults: [String: Set<String>] = [:]
// Map the Legacy types for the NSKeyedUnarchiver
NSKeyedUnarchiver.setClass(
@ -23,14 +35,16 @@ enum _003_YDBToGRDBMigration: Migration {
forClassName: "SessionSnodeKit.Snode"
)
Storage.read { transaction in
// Process the lastSnodePoolRefreshDate
dbConnection.read { transaction in
// MARK: --lastSnodePoolRefreshDate
lastSnodePoolRefreshDate = transaction.object(
forKey: SSKLegacy.lastSnodePoolRefreshDateKey,
inCollection: SSKLegacy.lastSnodePoolRefreshDateCollection
) as? Date
// Process the OnionRequestPaths
// MARK: --OnionRequestPaths
if
let path0Snode0 = transaction.object(forKey: "0-0", inCollection: SSKLegacy.onionRequestPathCollection) as? SSKLegacy.Snode,
let path0Snode1 = transaction.object(forKey: "0-1", inCollection: SSKLegacy.onionRequestPathCollection) as? SSKLegacy.Snode,
@ -52,21 +66,44 @@ enum _003_YDBToGRDBMigration: Migration {
snodeSetResult["\(SnodeSet.onionRequestPathPrefix)1"] = [ path1Snode0, path1Snode1, path1Snode2 ]
}
}
GRDBStorage.shared.update(progress: 0.02, for: self, in: target)
// MARK: --SnodePool
// Process the SnodePool
transaction.enumerateKeysAndObjects(inCollection: SSKLegacy.snodePoolCollection) { _, object, _ in
guard let snode = object as? SSKLegacy.Snode else { return }
snodeResult.insert(snode)
}
// Process the Swarms
var swarmCollections: Set<String> = []
// MARK: --Swarms
// Note: There is no index on the collection column so unfortunately it takes the same amount of
// time to enumerate through all collections as it does to just get the count of collections, as
// a result if the database is very large this part can be slow (~15s with 2,000,000 rows) - we
// want to show some kind of progress while doing this enumeration so the below code includes a
// number of rough values to show some kind of progression while the enumeration occurs (most users
// won't run into issues with this at all)
var swarmCollections: Set<String> = []
let startProgress: CGFloat = 0.02
let swarmCompleteProgress: CGFloat = 0.90
let interEnumerationMaxProgress: CGFloat = ((swarmCompleteProgress - startProgress) * 0.8)
let maxCollectionsEstimate: CGFloat = 1000
let numCollectionsToTriggerProgressUpdate: CGFloat = 20
var collectionIndex: CGFloat = 0
var oldProgress: CGFloat = startProgress
transaction.enumerateCollections { collectionName, _ in
if collectionName.starts(with: SSKLegacy.swarmCollectionPrefix) {
swarmCollections.insert(collectionName.substring(from: SSKLegacy.swarmCollectionPrefix.count))
}
collectionIndex += 1
if collectionIndex.truncatingRemainder(dividingBy: numCollectionsToTriggerProgressUpdate) == 0 {
oldProgress = (startProgress + (interEnumerationMaxProgress * (collectionIndex / maxCollectionsEstimate)))
GRDBStorage.shared.update(progress: oldProgress, for: self, in: target)
}
}
GRDBStorage.shared.update(progress: swarmCompleteProgress, for: self, in: target)
for swarmCollection in swarmCollections {
let collection: String = "\(SSKLegacy.swarmCollectionPrefix)\(swarmCollection)"
@ -77,13 +114,39 @@ enum _003_YDBToGRDBMigration: Migration {
snodeSetResult[swarmCollection] = (snodeSetResult[swarmCollection] ?? Set()).inserting(snode)
}
}
GRDBStorage.shared.update(progress: 0.92, for: self, in: target)
// MARK: --Received message hashes
transaction.enumerateKeysAndObjects(inCollection: SSKLegacy.receivedMessagesCollection) { key, object, _ in
guard let hashSet = object as? Set<String> else { return }
receivedMessageResults[key] = hashSet
}
GRDBStorage.shared.update(progress: 0.93, for: self, in: target)
// MARK: --Last message info
transaction.enumerateKeysAndObjects(inCollection: SSKLegacy.lastMessageHashCollection) { key, object, _ in
guard let lastMessageJson = object as? JSON else { return }
guard let lastMessageHash: String = lastMessageJson["hash"] as? String else { return }
// Note: We remove the value from 'receivedMessageResults' as we want to try and use
// it's actual 'expirationDate' value
lastMessageResults[key] = (lastMessageHash, lastMessageJson)
receivedMessageResults[key] = receivedMessageResults[key]?.removing(lastMessageHash)
}
GRDBStorage.shared.update(progress: 0.94, for: self, in: target)
}
// Insert the data into GRDB
// MARK: - Insert into GRDB
try autoreleasepool {
// MARK: --lastSnodePoolRefreshDate
db[.lastSnodePoolRefreshDate] = lastSnodePoolRefreshDate
// MARK: --SnodePool
try snodeResult.forEach { legacySnode in
try Snode(
address: legacySnode.address,
@ -92,6 +155,9 @@ enum _003_YDBToGRDBMigration: Migration {
x25519PublicKey: legacySnode.publicKeySet.x25519Key
).insert(db)
}
GRDBStorage.shared.update(progress: 0.96, for: self, in: target)
// MARK: --SnodeSets
try snodeSetResult.forEach { key, legacySnodeSet in
try legacySnodeSet.enumerated().forEach { nodeIndex, legacySnode in
@ -104,34 +170,12 @@ enum _003_YDBToGRDBMigration: Migration {
).insert(db)
}
}
GRDBStorage.shared.update(progress: 0.98, for: self, in: target)
}
// MARK: - Received Messages & Last Message Hash
var lastMessageResults: [String: (hash: String, json: JSON)] = [:]
var receivedMessageResults: [String: Set<String>] = [:]
// TODO: Move into the top read block???
Storage.read { transaction in
// Extract the received message hashes
transaction.enumerateKeysAndObjects(inCollection: SSKLegacy.receivedMessagesCollection) { key, object, _ in
guard let hashSet = object as? Set<String> else { return }
receivedMessageResults[key] = hashSet
}
// Retrieve the last message info
transaction.enumerateKeysAndObjects(inCollection: SSKLegacy.lastMessageHashCollection) { key, object, _ in
guard let lastMessageJson = object as? JSON else { return }
guard let lastMessageHash: String = lastMessageJson["hash"] as? String else { return }
// Note: We remove the value from 'receivedMessageResults' as we want to try and use
// it's actual 'expirationDate' value
lastMessageResults[key] = (lastMessageHash, lastMessageJson)
receivedMessageResults[key] = receivedMessageResults[key]?.removing(lastMessageHash)
}
}
try autoreleasepool {
// MARK: --Received Messages
try receivedMessageResults.forEach { key, hashes in
try hashes.forEach { hash in
_ = try SnodeReceivedMessageInfo(
@ -141,6 +185,9 @@ enum _003_YDBToGRDBMigration: Migration {
).inserted(db)
}
}
GRDBStorage.shared.update(progress: 0.99, for: self, in: target)
// MARK: --Last Message Hash
try lastMessageResults.forEach { key, data in
let expirationDateMs: Int64 = ((data.json["expirationDate"] as? Int64) ?? 0)

View File

@ -170,6 +170,7 @@ public final class GRDBStorage {
self.migrator?.asyncMigrate(dbPool) { [weak self] _, error in
self?.hasCompletedMigrations = true
self?.migrationProgressUpdater = nil
SUKLegacy.clearLegacyDatabaseInstance()
onComplete((error == nil), needsConfigSync)
}

View File

@ -1,8 +1,17 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import YapDatabase
public enum SUKLegacy {
// MARK: - YapDatabase
private static let keychainService = "TSKeyChainService"
private static let keychainDBCipherKeySpec = "OWSDatabaseCipherKeySpec"
private static let sqlCipherKeySpecLength = 48
private static var database: Atomic<YapDatabase>?
// MARK: - Collections and Keys
internal static let userAccountRegisteredNumberKey = "TSStorageRegisteredNumberKey"
@ -14,6 +23,103 @@ public enum SUKLegacy {
internal static let identityKeyStoreIdentityKey = "TSStorageManagerIdentityKeyStoreIdentityKey"
internal static let identityKeyStoreCollection = "TSStorageManagerIdentityKeyStoreCollection"
// MARK: - Database Functions
private static var legacyDatabaseFilepath: String {
let sharedDirUrl: URL = URL(fileURLWithPath: OWSFileSystem.appSharedDataDirectoryPath())
return sharedDirUrl
.appendingPathComponent("database")
.appendingPathComponent("Signal.sqlite")
.path
}
private static let legacyDatabaseDeserializer: YapDatabaseDeserializer = {
return { (collection: String, key: String, data: Data) -> Any in
/// **Note:** The old `init(forReadingWith:)` method has been deprecated with `init(forReadingFrom:)`
/// and Apple changed the default of `requiresSecureCoding` to be true, this results in some of the types from failing
/// to decode, as a result we need to set it to false here
let unarchiver: NSKeyedUnarchiver? = try? NSKeyedUnarchiver(forReadingFrom: data)
unarchiver?.requiresSecureCoding = false
guard !data.isEmpty, let result = unarchiver?.decodeObject(forKey: "root") else {
return UnknownDBObject()
}
return result
}
}()
public static var hasLegacyDatabaseFile: Bool {
return FileManager.default.fileExists(atPath: legacyDatabaseFilepath)
}
@discardableResult public static func loadDatabaseIfNeeded() -> Bool {
guard SUKLegacy.database == nil else { return true }
/// Ensure the databaseKeySpec exists
var maybeKeyData: Data? = try? CurrentAppContext().keychainStorage().data(
forService: keychainService,
key: keychainDBCipherKeySpec
)
defer { if maybeKeyData != nil { maybeKeyData!.resetBytes(in: 0..<maybeKeyData!.count) } }
guard maybeKeyData != nil, maybeKeyData?.count == sqlCipherKeySpecLength else { return false }
// Setup the database options
let options: YapDatabaseOptions = YapDatabaseOptions()
options.corruptAction = .fail
options.enableMultiProcessSupport = true
options.cipherUnencryptedHeaderLength = kSqliteHeaderLength // Needed for iOS to support SQLite writes
options.legacyCipherCompatibilityVersion = 3 // Old DB was SQLCipher V3
options.cipherKeySpecBlock = {
/// To avoid holding the keySpec in memory too long we load it as needed, since we have already confirmed
/// it's existence we can force-try here (the database will crash if it's invalid anyway)
var keySpec: Data = try! CurrentAppContext().keychainStorage().data(
forService: keychainService,
key: keychainDBCipherKeySpec
)
defer { keySpec.resetBytes(in: 0..<keySpec.count) }
return keySpec
}
let maybeDatabase: YapDatabase? = YapDatabase(
path: legacyDatabaseFilepath,
serializer: nil,
deserializer: legacyDatabaseDeserializer,
options: options
)
guard let database: YapDatabase = maybeDatabase else { return false }
// Store the database instance atomically
SUKLegacy.database = Atomic(database)
return true
}
public static func newDatabaseConnection() -> YapDatabaseConnection? {
SUKLegacy.loadDatabaseIfNeeded()
return self.database?.wrappedValue.newConnection()
}
public static func clearLegacyDatabaseInstance() {
self.database = nil
}
// MARK: - UnknownDBObject
@objc(LegacyUnknownDBObject)
public class UnknownDBObject: NSObject, NSCoding {
override public init() {}
public required init?(coder: NSCoder) {}
public func encode(with coder: NSCoder) { fatalError("Shouldn't be encoding this type") }
}
// MARK: - LagacyKeyPair
@objc(LegacyKeyPair)
public class KeyPair: NSObject, NSCoding {
private static let keyLength: Int = 32

View File

@ -4,9 +4,10 @@ import Foundation
import GRDB
enum _001_InitialSetupMigration: Migration {
static let target: TargetMigrations.Identifier = .utilitiesKit
static let identifier: String = "initialSetup"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
try db.create(table: Identity.self) { t in

View File

@ -7,9 +7,10 @@ import Curve25519Kit
/// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration
/// before running the `YDBToGRDBMigration`
enum _002_SetupStandardJobs: Migration {
static let target: TargetMigrations.Identifier = .utilitiesKit
static let identifier: String = "SetupStandardJobs"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
try autoreleasepool {

View File

@ -4,12 +4,18 @@ import Foundation
import GRDB
enum _003_YDBToGRDBMigration: Migration {
static let target: TargetMigrations.Identifier = .utilitiesKit
static let identifier: String = "YDBToGRDBMigration"
static let minExpectedRunDuration: TimeInterval = 0.1
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
// MARK: - Identity keys
guard let dbConnection: YapDatabaseConnection = SUKLegacy.newDatabaseConnection() else {
SNLog("[Migration Warning] No legacy database, skipping \(target.key(with: self))")
return
}
// MARK: - Read from Legacy Database
// Note: Want to exclude the Snode's we already added from the 'onionRequestPathResult'
var registeredNumber: String?
@ -24,7 +30,9 @@ enum _003_YDBToGRDBMigration: Migration {
forClassName: "ECKeyPair"
)
Storage.read { transaction in
dbConnection.read { transaction in
// MARK: --Identity keys
registeredNumber = transaction.object(
forKey: SUKLegacy.userAccountRegisteredNumberKey,
inCollection: SUKLegacy.userAccountCollection
@ -73,9 +81,12 @@ enum _003_YDBToGRDBMigration: Migration {
throw StorageError.migrationFailed
}
print("RAWR publicKey \(userX25519KeyPair.publicKey.toHexString())")
// MARK: - Insert into GRDB
try autoreleasepool {
// Insert the data into GRDB
// MARK: --Identity keys
try Identity(
variant: .seed,
data: Data(hex: seedHexString)

View File

@ -4,9 +4,20 @@ import Foundation
import GRDB
public protocol Migration {
static var target: TargetMigrations.Identifier { get }
static var identifier: String { get }
static var needsConfigSync: Bool { get }
static var minExpectedRunDuration: TimeInterval { get }
static func migrate(_ db: Database) throws
}
public extension Migration {
static func loggedMigrate(_ targetIdentifier: TargetMigrations.Identifier) -> ((_ db: Database) throws -> ()) {
return { (db: Database) in
SNLog("[Migration Info] Starting \(targetIdentifier.key(with: self))")
try migrate(db)
SNLog("[Migration Info] Completed \(targetIdentifier.key(with: self))")
}
}
}

View File

@ -866,7 +866,7 @@ public class AssociatedRecord<T, PagedType>: ErasedAssociatedRecord where T: Fet
self.associateData = associateData
}
convenience init<Table: TableRecord>(
public convenience init<Table: TableRecord>(
trackedAgainst: Table.Type,
observedChanges: [PagedData.ObservedChanges],
dataQuery: @escaping (SQL?) -> SQLRequest<T>,

View File

@ -27,7 +27,7 @@ public struct TargetMigrations: Comparable {
return (lhsIndex < rhsIndex)
}
func key(with migration: Migration.Type) -> String {
public func key(with migration: Migration.Type) -> String {
return "\(self.rawValue).\(migration.identifier)"
}
}
@ -43,6 +43,10 @@ public struct TargetMigrations: Comparable {
identifier: Identifier,
migrations: [MigrationSet]
) {
guard !migrations.contains(where: { migration in migration.contains(where: { $0.target != identifier }) }) else {
preconditionFailure("Attempted to register a migration with the wrong target")
}
self.identifier = identifier
self.migrations = migrations
}

View File

@ -4,7 +4,10 @@ import Foundation
import GRDB
public extension DatabaseMigrator {
mutating func registerMigration(_ identifier: TargetMigrations.Identifier, migration: Migration.Type, foreignKeyChecks: ForeignKeyChecks = .deferred) {
self.registerMigration("\(identifier).\(migration.identifier)", migrate: migration.migrate)
mutating func registerMigration(_ targetIdentifier: TargetMigrations.Identifier, migration: Migration.Type, foreignKeyChecks: ForeignKeyChecks = .deferred) {
self.registerMigration(
targetIdentifier.key(with: migration),
migrate: migration.loggedMigrate(targetIdentifier)
)
}
}

View File

@ -1,9 +0,0 @@
#import <Foundation/Foundation.h>
@interface NSArray (Functional)
- (BOOL)contains:(BOOL (^)(id))predicate;
- (NSArray *)filtered:(BOOL (^)(id))isIncluded;
- (NSArray *)map:(id (^)(id))transform;
@end

View File

@ -1,32 +0,0 @@
#import "NSArray+Functional.h"
@implementation NSArray (Functional)
- (BOOL)contains:(BOOL (^)(id))predicate {
for (id object in self) {
BOOL isPredicateSatisfied = predicate(object);
if (isPredicateSatisfied) { return YES; }
}
return NO;
}
- (NSArray *)filtered:(BOOL (^)(id))isIncluded {
NSMutableArray *result = [NSMutableArray new];
for (id object in self) {
if (isIncluded(object)) {
[result addObject:object];
}
}
return result;
}
- (NSArray *)map:(id (^)(id))transform {
NSMutableArray *result = [NSMutableArray new];
for (id object in self) {
id transformedObject = transform(object);
[result addObject:transformedObject];
}
return result;
}
@end

View File

@ -52,6 +52,7 @@ public final class JobRunner {
let messageSendQueue: JobQueue = JobQueue(
type: .messageSend,
executionType: .concurrent, // Allow as many jobs to run at once as supported by the device
qos: .default,
jobVariants: [
jobVariants.remove(.attachmentUpload),
@ -62,6 +63,7 @@ public final class JobRunner {
)
let messageReceiveQueue: JobQueue = JobQueue(
type: .messageReceive,
executionType: .concurrent, // Allow as many jobs to run at once as supported by the device
qos: .default,
jobVariants: [
jobVariants.remove(.messageReceive)
@ -320,33 +322,48 @@ private final class JobQueue {
}
}
fileprivate enum ExecutionType {
/// A serial queue will execute one job at a time until the queue is empty, then will load any new/deferred
/// jobs and run those one at a time
case serial
/// A concurrent queue will execute as many jobs as the device supports at once until the queue is empty,
/// then will load any new/deferred jobs and try to start them all
case concurrent
}
private class Trigger {
private weak var queue: JobQueue?
private var timer: Timer?
fileprivate var fireTimestamp: TimeInterval = 0
static func create(queue: JobQueue, timestamp: TimeInterval) -> Trigger? {
// Setup the trigger (wait at least 1 second before triggering)
/// Setup the trigger (wait at least 1 second before triggering)
///
/// **Note:** We use the `Timer.scheduledTimerOnMainThread` method because running a timer
/// on our random queue threads results in the timer never firing, the `start` method will redirect itself to
/// the correct thread
let trigger: Trigger = Trigger()
trigger.queue = queue
trigger.timer = Timer.scheduledTimer(
timeInterval: max(1, (timestamp - Date().timeIntervalSince1970)),
target: self,
selector: #selector(start),
userInfo: nil,
repeats: false
trigger.fireTimestamp = max(1, (timestamp - Date().timeIntervalSince1970))
trigger.timer = Timer.scheduledTimerOnMainThread(
withTimeInterval: trigger.fireTimestamp,
repeats: false,
block: { [weak queue] _ in
queue?.start()
}
)
return trigger
}
deinit { timer?.invalidate() }
@objc func start() {
queue?.start()
func invalidate() {
// Need to do this to prevent a strong reference cycle
timer?.invalidate()
timer = nil
}
}
private let type: QueueType
private let executionType: ExecutionType
private let qosClass: DispatchQoS
private let queueKey: DispatchSpecificKey = DispatchSpecificKey<String>()
private let queueContext: String
@ -360,7 +377,7 @@ private final class JobQueue {
let result: DispatchQueue = DispatchQueue(
label: self.queueContext,
qos: self.qosClass,
attributes: [],
attributes: (self.executionType == .concurrent ? [.concurrent] : []),
autoreleaseFrequency: .inherit,
target: nil
)
@ -378,8 +395,15 @@ private final class JobQueue {
// MARK: - Initialization
init(type: QueueType, qos: DispatchQoS, jobVariants: [Job.Variant], onQueueDrained: (() -> ())? = nil) {
init(
type: QueueType,
executionType: ExecutionType = .serial,
qos: DispatchQoS,
jobVariants: [Job.Variant],
onQueueDrained: (() -> ())? = nil
) {
self.type = type
self.executionType = executionType
self.queueContext = "JobQueue-\(type.name)"
self.qosClass = qos
self.jobVariants = jobVariants
@ -483,10 +507,10 @@ private final class JobQueue {
// MARK: - Job Running
fileprivate func start() {
fileprivate func start(force: Bool = false) {
// We only want the JobRunner to run in the main app
guard CurrentAppContext().isMainApp else { return }
guard !isRunning.wrappedValue else { return }
guard force || !isRunning.wrappedValue else { return }
// The JobRunner runs synchronously we need to ensure this doesn't start
// on the main thread (if it is on the main thread then swap to a different thread)
@ -497,9 +521,21 @@ private final class JobQueue {
return
}
// Flag the JobRunner as running (to prevent something else from trying to start it
// and messing with the execution behaviour)
var wasAlreadyRunning: Bool = false
isRunning.mutate { isRunning in
wasAlreadyRunning = isRunning
isRunning = true
}
// Get any pending jobs
let jobIdsAlreadyRunning: Set<Int64> = jobsCurrentlyRunning.wrappedValue
let jobsAlreadyInQueue: Set<Int64> = queue.wrappedValue.compactMap { $0.id }.asSet()
let jobsToRun: [Job] = GRDBStorage.shared.read { db in
try Job.filterPendingJobs(variants: jobVariants)
.filter(!jobIdsAlreadyRunning.contains(Job.Columns.id)) // Exclude jobs already running
.filter(!jobsAlreadyInQueue.contains(Job.Columns.id)) // Exclude jobs already in the queue
.fetchAll(db)
}
.defaulting(to: [])
@ -508,28 +544,24 @@ private final class JobQueue {
var jobCount: Int = 0
queue.mutate { queue in
// Avoid re-adding jobs to the queue that are already in it
let jobsNotAlreadyInQueue: [Job] = jobsToRun
.filter { job in !queue.contains(where: { $0.id == job.id }) }
// Add the jobs to the queue
if !jobsNotAlreadyInQueue.isEmpty {
queue.append(contentsOf: jobsToRun)
}
queue.append(contentsOf: jobsToRun)
jobCount = queue.count
}
// If there are no pending jobs then schedule the JobRunner to start again
// when the next scheduled job should start
// If there are no pending jobs and nothing in the queue then schedule the JobRunner
// to start again when the next scheduled job should start
guard jobCount > 0 else {
isRunning.mutate { $0 = false }
scheduleNextSoonestJob()
if jobIdsAlreadyRunning.isEmpty {
isRunning.mutate { $0 = false }
scheduleNextSoonestJob()
}
return
}
// Run the first job in the queue
SNLog("[JobRunner] Starting \(queueContext) with (\(jobCount) job\(jobCount != 1 ? "s" : ""))")
if !wasAlreadyRunning {
SNLog("[JobRunner] Starting \(queueContext) with (\(jobCount) job\(jobCount != 1 ? "s" : ""))")
}
runNextJob()
}
@ -542,7 +574,13 @@ private final class JobQueue {
return
}
guard let (nextJob, numJobsRemaining): (Job, Int) = queue.mutate({ queue in queue.popFirst().map { ($0, queue.count) } }) else {
isRunning.mutate { $0 = false }
// If it's a serial queue, or there are no more jobs running then update the 'isRunning' flag
if executionType != .concurrent || jobsCurrentlyRunning.wrappedValue.isEmpty {
isRunning.mutate { $0 = false }
}
// Always attempt to schedule the next soonest job (otherwise if enough jobs get started in rapid
// succession then pending/failed jobs in the database may never get re-started in a concurrent queue)
scheduleNextSoonestJob()
return
}
@ -606,15 +644,20 @@ private final class JobQueue {
return
}
// Otherwise re-add the current job after it's dependencies
queue.mutate { queue in
guard let lastDependencyIndex: Int = queue.lastIndex(where: { jobDependencyIds.contains($0.id ?? -1) }) else {
queue.append(nextJob)
return
// Otherwise re-add the current job after it's dependencies (if this isn't a concurrent
// queue - don't want to immediately try to start the job again only for it to end up back
// in here)
if executionType != .concurrent {
queue.mutate { queue in
guard let lastDependencyIndex: Int = queue.lastIndex(where: { jobDependencyIds.contains($0.id ?? -1) }) else {
queue.append(nextJob)
return
}
queue.insert(nextJob, at: lastDependencyIndex + 1)
}
queue.insert(nextJob, at: lastDependencyIndex + 1)
}
handleJobDeferred(nextJob)
return
}
@ -624,10 +667,17 @@ private final class JobQueue {
// Note: We need to store 'numJobsRemaining' in it's own variable because
// the 'SNLog' seems to dispatch to it's own queue which ends up getting
// blocked by the JobRunner's queue becuase 'jobQueue' is Atomic
nextTrigger.mutate { $0 = nil }
var numJobsRunning: Int = 0
nextTrigger.mutate { trigger in
trigger?.invalidate() // Need to invalidate to prevent a memory leak
trigger = nil
}
isRunning.mutate { $0 = true }
jobsCurrentlyRunning.mutate { $0 = $0.inserting(nextJob.id) }
SNLog("[JobRunner] \(queueContext) started job (\(numJobsRemaining) remaining)")
jobsCurrentlyRunning.mutate { jobsCurrentlyRunning in
jobsCurrentlyRunning = jobsCurrentlyRunning.inserting(nextJob.id)
numJobsRunning = jobsCurrentlyRunning.count
}
SNLog("[JobRunner] \(queueContext) started job (\(executionType == .concurrent ? "\(numJobsRunning) currently running, " : "")\(numJobsRemaining) remaining)")
jobExecutor.run(
nextJob,
@ -635,6 +685,14 @@ private final class JobQueue {
failure: handleJobFailed,
deferred: handleJobDeferred
)
// If this queue executes concurrently and there are still jobs remaining then immediately attempt
// to start the next job
if executionType == .concurrent && numJobsRemaining > 0 {
internalQueue.async { [weak self] in
self?.runNextJob()
}
}
}
private func scheduleNextSoonestJob() {
@ -647,7 +705,9 @@ private final class JobQueue {
// If there are no remaining jobs the trigger the 'onQueueDrained' callback and stop
guard let nextJobTimestamp: TimeInterval = nextJobTimestamp else {
self.onQueueDrained?()
if executionType != .concurrent || jobsCurrentlyRunning.wrappedValue.isEmpty {
self.onQueueDrained?()
}
return
}
@ -655,17 +715,33 @@ private final class JobQueue {
let secondsUntilNextJob: TimeInterval = (nextJobTimestamp - Date().timeIntervalSince1970)
guard secondsUntilNextJob > 0 else {
SNLog("[JobRunner] Restarting \(queueContext) immediately for job scheduled \(Int(ceil(abs(secondsUntilNextJob)))) second\(Int(ceil(abs(secondsUntilNextJob))) == 1 ? "" : "s")) ago")
// Only log that the queue is getting restarted if this queue had actually been about to stop
if executionType != .concurrent || jobsCurrentlyRunning.wrappedValue.isEmpty {
let timingString: String = (nextJobTimestamp == 0 ?
"that should be in the queue" :
"scheduled \(Int(ceil(abs(secondsUntilNextJob)))) second\(Int(ceil(abs(secondsUntilNextJob))) == 1 ? "" : "s") ago"
)
SNLog("[JobRunner] Restarting \(queueContext) immediately for job \(timingString)")
}
// Trigger the 'start' function to load in any pending jobs that aren't already in the
// queue (for concurrent queues we want to force them to load in pending jobs and add
// them to the queue regardless of whether the queue is already running)
internalQueue.async { [weak self] in
self?.start()
self?.start(force: (self?.executionType == .concurrent))
}
return
}
// Only schedule a trigger if this queue has actually completed
guard executionType != .concurrent || jobsCurrentlyRunning.wrappedValue.isEmpty else { return }
// Setup a trigger
SNLog("[JobRunner] Stopping \(queueContext) until next job in \(Int(ceil(abs(secondsUntilNextJob)))) second\(Int(ceil(abs(secondsUntilNextJob))) == 1 ? "" : "s"))")
nextTrigger.mutate { $0 = Trigger.create(queue: self, timestamp: nextJobTimestamp) }
SNLog("[JobRunner] Stopping \(queueContext) until next job in \(Int(ceil(abs(secondsUntilNextJob)))) second\(Int(ceil(abs(secondsUntilNextJob))) == 1 ? "" : "s")")
nextTrigger.mutate { trigger in
trigger?.invalidate() // Need to invalidate the old trigger to prevent a memory leak
trigger = Trigger.create(queue: self, timestamp: nextJobTimestamp)
}
}
// MARK: - Handling Results
@ -708,6 +784,29 @@ private final class JobQueue {
default: break
}
// For concurrent queues retrieve any 'dependant' jobs and re-add them here (if they have other
// dependencies they will be removed again when they try to execute)
if executionType == .concurrent {
let dependantJobs: [Job] = GRDBStorage.shared
.read { db in try job.dependantJobs.fetchAll(db) }
.defaulting(to: [])
let dependantJobIds: [Int64] = dependantJobs
.compactMap { $0.id }
let jobIdsNotInQueue: Set<Int64> = dependantJobIds
.asSet()
.subtracting(queue.wrappedValue.compactMap { $0.id })
// If there are dependant jobs which aren't in the queue we should just append them
if !jobIdsNotInQueue.isEmpty {
queue.mutate { queue in
queue.append(
contentsOf: dependantJobs
.filter { jobIdsNotInQueue.contains($0.id ?? -1) }
)
}
}
}
// The job is removed from the queue before it runs so all we need to to is remove it
// from the 'currentlyRunning' set and start the next one
jobsCurrentlyRunning.mutate { $0 = $0.removing(job.id) }
@ -732,6 +831,7 @@ private final class JobQueue {
// If this is the blocking queue and a "blocking" job failed then rerun it immediately
if self.type == .blocking && job.shouldBlockFirstRunEachSession {
SNLog("[JobRunner] \(queueContext) \(job.variant) job failed; retrying immediately")
jobsCurrentlyRunning.mutate { $0 = $0.removing(job.id) }
queue.mutate { $0.insert(job, at: 0) }
internalQueue.async { [weak self] in
@ -752,9 +852,24 @@ private final class JobQueue {
else {
SNLog("[JobRunner] \(queueContext) \(job.variant) failed permanently\(maxFailureCount >= 0 ? "; too many retries" : "")")
let dependantJobIds: [Int64] = try job.dependantJobs
.select(.id)
.asRequest(of: Int64.self)
.fetchAll(db)
// If the job permanently failed or we have performed all of our retry attempts
// then delete the job (it'll probably never succeed)
// then delete the job and all of it's dependant jobs (it'll probably never succeed)
_ = try job.dependantJobs
.deleteAll(db)
_ = try job.delete(db)
// Remove the dependant jobs from the queue (so we don't try to run a deleted job)
if !dependantJobIds.isEmpty {
queue.mutate { queue in
queue = queue.filter { !dependantJobIds.contains($0.id ?? -1) }
}
}
return
}
@ -783,7 +898,7 @@ private final class JobQueue {
.fetchAll(db)
// Remove the dependant jobs from the queue (so we don't get stuck in a loop of trying
// to run dependecies indefinitely
// to run dependecies indefinitely)
if !dependantJobIds.isEmpty {
queue.mutate { queue in
queue = queue.filter { !dependantJobIds.contains($0.id ?? -1) }

View File

@ -7,7 +7,6 @@ FOUNDATION_EXPORT const unsigned char SessionUtilitiesKitVersionString[];
#import <SessionUtilitiesKit/DataSource.h>
#import <SessionUtilitiesKit/LKGroupUtilities.h>
#import <SessionUtilitiesKit/MIMETypeUtil.h>
#import <SessionUtilitiesKit/NSArray+Functional.h>
#import <SessionUtilitiesKit/NSData+Image.h>
#import <SessionUtilitiesKit/NSNotificationCenter+OWS.h>
#import <SessionUtilitiesKit/NSString+SSK.h>

View File

@ -1,62 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import YapDatabase
import SessionMessagingKit
@objc(SNBlockingManagerRemovalMigration)
public class BlockingManagerRemovalMigration: OWSDatabaseMigration {
@objc
class func migrationId() -> String {
return "004"
}
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
self.doMigrationAsync(completion: completion)
}
private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) {
// These are the legacy keys that were used to persist the "block list" state
let kOWSBlockingManager_BlockListCollection: String = "kOWSBlockingManager_BlockedPhoneNumbersCollection"
let kOWSBlockingManager_BlockedPhoneNumbersKey: String = "kOWSBlockingManager_BlockedPhoneNumbersKey"
// Note: These will be done in the YDB to GRDB migration but have added it here to be safe
NSKeyedUnarchiver.setClass(
SMKLegacy._Contact.self,
forClassName: "SNContact"
)
let dbConnection: YapDatabaseConnection = primaryStorage.newDatabaseConnection()
let blockedSessionIds: Set<String> = Set(dbConnection.object(
forKey: kOWSBlockingManager_BlockedPhoneNumbersKey,
inCollection: kOWSBlockingManager_BlockListCollection
) as? [String] ?? [])
Storage.write(
with: { transaction in
var result: Set<SMKLegacy._Contact> = []
transaction.enumerateRows(inCollection: SMKLegacy.contactCollection) { _, object, _, _ in
guard let contact = object as? SMKLegacy._Contact else { return }
result.insert(contact)
}
result
.filter { contact -> Bool in blockedSessionIds.contains(contact.sessionID) }
.forEach { contact in
contact.isBlocked = true
transaction.setObject(contact, forKey: contact.sessionID, inCollection: SMKLegacy.contactCollection)
}
// Now that the values have been migrated we can clear out the old collection
transaction.removeAllObjects(inCollection: kOWSBlockingManager_BlockListCollection)
self.save(with: transaction) // Intentionally capture self
},
completion: {
completion(true, true)
}
)
}
}

View File

@ -1,57 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import YapDatabase
import SessionMessagingKit
@objc(SNContactsMigration)
public class ContactsMigration: OWSDatabaseMigration {
@objc
class func migrationId() -> String {
return "001"
}
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
self.doMigrationAsync(completion: completion)
}
private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) {
var contacts: [SMKLegacy._Contact] = []
// Note: These will be done in the YDB to GRDB migration but have added it here to be safe
NSKeyedUnarchiver.setClass(
SMKLegacy._Thread.self,
forClassName: "TSThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ContactThread.self,
forClassName: "TSContactThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Contact.self,
forClassName: "SNContact"
)
Storage.read { transaction in
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.threadCollection) { _, object, _ in
guard let thread = object as? SMKLegacy._ContactThread else { return }
let sessionId: String = SMKLegacy._ContactThread.contactSessionId(fromThreadId: thread.uniqueId)
let contact: SMKLegacy._Contact? = transaction.object(forKey: sessionId, inCollection: SMKLegacy.contactCollection) as? SMKLegacy._Contact
contact?.isTrusted = true
contacts = contacts.appending(contact)
}
}
Storage.write(with: { transaction in
contacts.forEach { contact in
transaction.setObject(contact, forKey: contact.sessionID, inCollection: SMKLegacy.contactCollection)
}
self.save(with: transaction) // Intentionally capture self
}, completion: {
completion(true, false)
})
}
}

View File

@ -1,93 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import YapDatabase
import SessionMessagingKit
@objc(SNMessageRequestsMigration)
public class MessageRequestsMigration: OWSDatabaseMigration {
@objc
class func migrationId() -> String {
return "002"
}
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
self.doMigrationAsync(completion: completion)
}
private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) {
let userPublicKey: String = getUserHexEncodedPublicKey()
var contacts: Set<SMKLegacy._Contact> = Set()
var threads: [SMKLegacy._Thread] = []
// Note: These will be done in the YDB to GRDB migration but have added it here to be safe
NSKeyedUnarchiver.setClass(
SMKLegacy._Thread.self,
forClassName: "TSThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ContactThread.self,
forClassName: "TSContactThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._GroupThread.self,
forClassName: "TSGroupThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._GroupModel.self,
forClassName: "TSGroupModel"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._Contact.self,
forClassName: "SNContact"
)
Storage.read { transaction in
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.threadCollection) { _, object, _ in
guard let thread: SMKLegacy._Thread = object as? SMKLegacy._Thread else { return }
if thread is SMKLegacy._ContactThread {
let sessionId: String = SMKLegacy._ContactThread.contactSessionId(fromThreadId: thread.uniqueId)
if let contact: SMKLegacy._Contact = transaction.object(forKey: sessionId, inCollection: SMKLegacy.contactCollection) as? SMKLegacy._Contact {
contact.isApproved = true
contact.didApproveMe = true
contacts.insert(contact)
}
}
else if let groupThread: SMKLegacy._GroupThread = thread as? SMKLegacy._GroupThread, groupThread.isClosedGroup {
let groupAdmins: [String] = groupThread.groupModel.groupAdminIds
groupAdmins.forEach { sessionId in
if let contact: SMKLegacy._Contact = transaction.object(forKey: sessionId, inCollection: SMKLegacy.contactCollection) as? SMKLegacy._Contact {
contact.isApproved = true
contact.didApproveMe = true
contacts.insert(contact)
}
}
}
threads.append(thread)
}
if let user = transaction.object(forKey: userPublicKey, inCollection: SMKLegacy.contactCollection) as? SMKLegacy._Contact {
user.isApproved = true
user.didApproveMe = true
contacts.insert(user)
}
}
Storage.write(with: { transaction in
contacts.forEach { contact in
transaction.setObject(contact, forKey: contact.sessionID, inCollection: SMKLegacy.contactCollection)
}
threads.forEach { thread in
transaction.setObject(thread, forKey: thread.uniqueId, inCollection: SMKLegacy.threadCollection)
}
self.save(with: transaction) // Intentionally capture self
}, completion: {
completion(true, true)
})
}
}

View File

@ -1,25 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SessionUtilitiesKit/TSYapDatabaseObject.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^OWSDatabaseMigrationCompletion)(BOOL success, BOOL requiresConfigurationSync);
@class OWSPrimaryStorage;
@interface OWSDatabaseMigration : TSYapDatabaseObject
@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage;
// Prefer nonblocking (async) migrations by overriding `runUpWithTransaction:` in a subclass.
// Blocking migrations running too long will crash the app, effectively bricking install
// because the user will never get past it.
// If you must write a launch-blocking migration, override runUp.
- (void)runUpWithCompletion:(OWSDatabaseMigrationCompletion)completion;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,109 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseMigration.h"
#import <SessionMessagingKit/OWSPrimaryStorage.h>
#import <SessionMessagingKit/SSKEnvironment.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@implementation OWSDatabaseMigration
#pragma mark - Dependencies
- (OWSPrimaryStorage *)primaryStorage
{
OWSAssertDebug(SSKEnvironment.shared.primaryStorage);
return SSKEnvironment.shared.primaryStorage;
}
#pragma mark -
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSLogInfo(@"marking migration as complete.");
[super saveWithTransaction:transaction];
}
- (instancetype)init
{
self = [super initWithUniqueId:[self.class migrationId]];
if (!self) {
return self;
}
return self;
}
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey
{
if ([propertyKey isEqualToString:@"primaryStorage"]) {
return MTLPropertyStorageNone;
} else {
return [super storageBehaviorForPropertyWithKey:propertyKey];
}
}
+ (NSString *)migrationId
{
OWSAbstractMethod();
return @"";
}
+ (NSString *)collection
{
// We want all subclasses in the same collection
return @"OWSDatabaseMigration";
}
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAbstractMethod();
}
- (void)runUpWithCompletion:(OWSDatabaseMigrationCompletion)completion
{
OWSAssertDebug(completion);
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self runUpWithTransaction:transaction];
}
completion:^{
OWSLogInfo(@"Completed migration %@", self.uniqueId);
[self save];
completion(true, false);
}];
}
#pragma mark - Database Connections
#ifdef DEBUG
+ (YapDatabaseConnection *)dbReadConnection
{
return self.dbReadWriteConnection;
}
+ (YapDatabaseConnection *)dbReadWriteConnection
{
return SSKEnvironment.shared.migrationDBConnection;
}
- (YapDatabaseConnection *)dbReadConnection
{
return OWSDatabaseMigration.dbReadConnection;
}
- (YapDatabaseConnection *)dbReadWriteConnection
{
return OWSDatabaseMigration.dbReadWriteConnection;
}
#endif
@end
NS_ASSUME_NONNULL_END

View File

@ -1,23 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
typedef void (^OWSDatabaseMigrationCompletion)(BOOL success, BOOL requiresConfigurationSync);
@interface OWSDatabaseMigrationRunner : NSObject
/**
* Run any outstanding version migrations.
*/
- (void)runAllOutstandingWithCompletion:(OWSDatabaseMigrationCompletion)completion;
/**
* On new installations, no need to migrate anything.
*/
- (void)assumeAllExistingMigrationsRun;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,124 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseMigrationRunner.h"
#import "OWSDatabaseMigration.h"
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SessionUtilitiesKit/AppContext.h>
NS_ASSUME_NONNULL_BEGIN
@implementation OWSDatabaseMigrationRunner
#pragma mark - Dependencies
- (OWSPrimaryStorage *)primaryStorage
{
OWSAssertDebug(SSKEnvironment.shared.primaryStorage);
return SSKEnvironment.shared.primaryStorage;
}
#pragma mark -
// This should all migrations which do NOT qualify as safeBlockingMigrations:
- (NSArray<OWSDatabaseMigration *> *)allMigrations
{
return @[
[SNOpenGroupServerIdLookupMigration new],
[SNMessageRequestsMigration new],
[SNContactsMigration new],
[SNBlockingManagerRemovalMigration new]
];
}
- (void)assumeAllExistingMigrationsRun
{
for (OWSDatabaseMigration *migration in self.allMigrations) {
OWSLogInfo(@"Skipping migration on new install: %@", migration);
[migration save];
}
}
- (void)runAllOutstandingWithCompletion:(OWSDatabaseMigrationCompletion)completion
{
[self removeUnknownMigrations];
[self runMigrations:[self.allMigrations mutableCopy]
prevWasSuccessful: true
prevNeedsConfigSync:false
completion:completion];
}
// Some users (especially internal users) will move back and forth between
// app versions. Whenever they move "forward" in the version history, we
// want them to re-run any new migrations. Therefore, when they move "backward"
// in the version history, we cull any unknown migrations.
- (void)removeUnknownMigrations
{
NSMutableSet<NSString *> *knownMigrationIds = [NSMutableSet new];
for (OWSDatabaseMigration *migration in self.allMigrations) {
[knownMigrationIds addObject:migration.uniqueId];
}
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSArray<NSString *> *savedMigrationIds = [transaction allKeysInCollection:OWSDatabaseMigration.collection];
NSMutableSet<NSString *> *unknownMigrationIds = [NSMutableSet new];
[unknownMigrationIds addObjectsFromArray:savedMigrationIds];
[unknownMigrationIds minusSet:knownMigrationIds];
for (NSString *unknownMigrationId in unknownMigrationIds) {
OWSLogInfo(@"Culling unknown migration: %@", unknownMigrationId);
[transaction removeObjectForKey:unknownMigrationId inCollection:OWSDatabaseMigration.collection];
}
}];
}
// Run migrations serially to:
//
// * Ensure predictable ordering.
// * Prevent them from interfering with each other (e.g. deadlock).
- (void)runMigrations:(NSMutableArray<OWSDatabaseMigration *> *)migrations
prevWasSuccessful:(BOOL)prevWasSuccessful
prevNeedsConfigSync:(BOOL)prevNeedsConfigSync
completion:(OWSDatabaseMigrationCompletion)completion
{
OWSAssertDebug(migrations);
OWSAssertDebug(completion);
// If there are no more migrations to run, complete.
if (migrations.count < 1) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(prevWasSuccessful, prevNeedsConfigSync);
});
return;
}
// Pop next migration from front of queue.
OWSDatabaseMigration *migration = migrations.firstObject;
[migrations removeObjectAtIndex:0];
// If migration has already been run, skip it.
if ([OWSDatabaseMigration fetchObjectWithUniqueID:migration.uniqueId] != nil) {
[self runMigrations:migrations
prevWasSuccessful:prevWasSuccessful
prevNeedsConfigSync:prevNeedsConfigSync
completion:completion];
return;
}
OWSLogInfo(@"Running migration: %@", migration);
[migration runUpWithCompletion:^(BOOL successful, BOOL needsConfigSync){
OWSLogInfo(@"Migration complete: %@", migration);
[self runMigrations:migrations
prevWasSuccessful:(prevWasSuccessful && successful)
prevNeedsConfigSync:(prevNeedsConfigSync || needsConfigSync)
completion:completion];
}];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,24 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/OWSDatabaseMigration.h>
NS_ASSUME_NONNULL_BEGIN
typedef BOOL (^DBRecordFilterBlock)(id record);
@class YapDatabaseConnection;
// Base class for migrations that resave all or a subset of
// records in a database collection.
@interface OWSResaveCollectionDBMigration : OWSDatabaseMigration
- (void)resaveDBCollection:(NSString *)collection
filter:(nullable DBRecordFilterBlock)filter
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(OWSDatabaseMigrationCompletion)completion;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,80 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSResaveCollectionDBMigration.h"
#import <YapDatabase/YapDatabaseConnection.h>
#import <YapDatabase/YapDatabaseTransaction.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@implementation OWSResaveCollectionDBMigration
- (void)resaveDBCollection:(NSString *)collection
filter:(nullable DBRecordFilterBlock)filter
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(OWSDatabaseMigrationCompletion)completion
{
OWSAssertDebug(collection.length > 0);
OWSAssertDebug(dbConnection);
OWSAssertDebug(completion);
NSMutableArray<NSString *> *recordIds = [NSMutableArray new];
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[recordIds addObjectsFromArray:[transaction allKeysInCollection:collection]];
OWSLogInfo(@"Migrating %lu records from: %@.", (unsigned long)recordIds.count, collection);
}
completion:^{
[self resaveBatch:recordIds
collection:collection
filter:filter
dbConnection:dbConnection
completion:completion];
}];
}
- (void)resaveBatch:(NSMutableArray<NSString *> *)recordIds
collection:(NSString *)collection
filter:(nullable DBRecordFilterBlock)filter
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(OWSDatabaseMigrationCompletion)completion
{
OWSAssertDebug(recordIds);
OWSAssertDebug(collection.length > 0);
OWSAssertDebug(dbConnection);
OWSAssertDebug(completion);
OWSLogVerbose(@"%lu", (unsigned long)recordIds.count);
if (recordIds.count < 1) {
completion(true, false);
return;
}
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
const int kBatchSize = 1000;
for (int i = 0; i < kBatchSize && recordIds.count > 0; i++) {
NSString *messageId = [recordIds lastObject];
[recordIds removeLastObject];
id record = [transaction objectForKey:messageId inCollection:collection];
if (filter && !filter(record)) {
continue;
}
TSYapDatabaseObject *entity = (TSYapDatabaseObject *)record;
[entity saveWithTransaction:transaction];
}
}
completion:^{
// Process the next batch.
[self resaveBatch:recordIds
collection:collection
filter:filter
dbConnection:dbConnection
completion:completion];
}];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,71 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import YapDatabase
import SessionMessagingKit
@objc(SNOpenGroupServerIdLookupMigration)
public class OpenGroupServerIdLookupMigration: OWSDatabaseMigration {
@objc
class func migrationId() -> String {
return "003"
}
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
self.doMigrationAsync(completion: completion)
}
private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) {
var lookups: [OpenGroupServerIdLookup] = []
// Note: These will be done in the YDB to GRDB migration but have added it here to be safe
NSKeyedUnarchiver.setClass(
SMKLegacy._Thread.self,
forClassName: "TSThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._ContactThread.self,
forClassName: "TSContactThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._GroupThread.self,
forClassName: "TSGroupThread"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._GroupModel.self,
forClassName: "TSGroupModel"
)
// TODO: Add, SMKLegacy._OpenGroup, SMKLegacy._TSMessage (and related)
Storage.write(with: { transaction in
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.threadCollection) { _, object, _ in
guard let thread = object as? SMKLegacy._GroupThread else { return }
guard let openGroup: OpenGroupV2 = Storage.shared.getV2OpenGroup(for: thread.uniqueId) else { return }
guard let interactionsByThread: YapDatabaseViewTransaction = transaction.ext(SMKLegacy.messageDatabaseViewExtensionName) as? YapDatabaseViewTransaction else {
return
}
interactionsByThread.enumerateKeysAndObjects(inGroup: thread.uniqueId) { _, _, object, _, _ in
guard let tsMessage: TSMessage = object as? TSMessage else { return }
guard let tsMessageId: String = tsMessage.uniqueId else { return }
lookups.append(
OpenGroupServerIdLookup(
server: openGroup.server,
room: openGroup.room,
serverId: tsMessage.openGroupServerMessageID,
tsMessageId: tsMessageId
)
)
}
}
lookups.forEach { lookup in
Storage.shared.addOpenGroupServerIdLookup(lookup, using: transaction)
}
self.save(with: transaction) // Intentionally capture self
}, completion: {
completion(true, false)
})
}
}

View File

@ -769,11 +769,9 @@ extension SignalAttachmentItem: GalleryRailItem {
func buildRailItemView() -> UIView {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
getThumbnailImage().map { image in
imageView.image = image
}.retainUntilComplete()
imageView.backgroundColor = UIColor.black.withAlphaComponent(0.33)
imageView.image = getThumbnailImage()
return imageView
}

View File

@ -61,22 +61,8 @@ class SignalAttachmentItem: Hashable {
return attachment.captionText
}
var imageSize: CGSize = .zero
func getThumbnailImage() -> Promise<UIImage> {
return DispatchQueue.global().async(.promise) { () -> UIImage in
guard let image = self.attachment.staticThumbnail() else {
throw SignalAttachmentItemError.noThumbnail
}
return image
}.tap { result in
switch result {
case .fulfilled(let image):
self.imageSize = image.size
case .rejected(let error):
owsFailDebug("failed with error: \(error)")
}
}
func getThumbnailImage() -> UIImage? {
return attachment.staticThumbnail()
}
// MARK: Hashable

View File

@ -7,18 +7,11 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
@import SessionSnodeKit;
@import SessionUtilitiesKit;
#import <SignalUtilitiesKit/AppSetup.h>
#import <SignalUtilitiesKit/AppVersion.h>
#import <SignalUtilitiesKit/ByteParser.h>
#import <SignalUtilitiesKit/FunctionalUtil.h>
#import <SignalUtilitiesKit/NSArray+OWS.h>
#import <SignalUtilitiesKit/NSAttributedString+OWS.h>
#import <SignalUtilitiesKit/NSObject+Casting.h>
#import <SignalUtilitiesKit/NSSet+Functional.h>
#import <SignalUtilitiesKit/NSURLSessionDataTask+StatusCode.h>
#import <SignalUtilitiesKit/OWSAnyTouchGestureRecognizer.h>
#import <SignalUtilitiesKit/OWSDatabaseMigration.h>
#import <SignalUtilitiesKit/OWSDatabaseMigrationRunner.h>
#import <SignalUtilitiesKit/OWSDispatch.h>
#import <SignalUtilitiesKit/OWSError.h>
#import <SignalUtilitiesKit/OWSFormat.h>
@ -35,4 +28,3 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
#import <SignalUtilitiesKit/UIFont+OWS.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/UIViewController+OWS.h>
#import <SignalUtilitiesKit/VersionMigrations.h>

View File

@ -129,9 +129,12 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate {
addSubview(scrollView)
scrollView.autoPinEdgesToSuperviewMargins()
scrollView.addSubview(stackView)
scrollView.addSubview(stackClippingView)
stackClippingView.addSubview(stackView)
stackClippingView.autoPinEdgesToSuperviewEdges()
stackClippingView.autoMatch(.height, to: .height, of: scrollView)
stackView.autoPinEdgesToSuperviewEdges()
stackView.autoMatch(.height, to: .height, of: scrollView)
}
public required init?(coder aDecoder: NSCoder) {
@ -145,6 +148,14 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate {
result.clipsToBounds = false
result.layoutMargins = .zero
result.isScrollEnabled = true
result.scrollIndicatorInsets = UIEdgeInsets(top: 0, leading: 0, bottom: -10, trailing: 0)
return result
}()
private let stackClippingView: UIView = {
let result: UIView = UIView()
result.clipsToBounds = true
return result
}()
@ -194,7 +205,7 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate {
completion: { [weak self] _ in
self?.stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
self?.stackView.frame = oldFrame
self?.isHidden = true
self?.stackClippingView.isHidden = true
self?.cellViews = []
}
)
@ -202,54 +213,74 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate {
}
// Otherwise slide it away, recreate it and then slide it back
var oldFrame: CGRect = self.stackView.frame
let newCellViews: [GalleryRailCellView] = buildCellViews(
items: album,
cellViewBuilder: cellViewBuilder
)
UIView.animate(
withDuration: (animationDuration / 2),
delay: 0,
options: .curveEaseIn,
animations: { [weak self] in
self?.stackView.frame = oldFrame.offsetBy(
dx: 0,
dy: oldFrame.height
)
},
completion: { [weak self] _ in
let animateOut: ((CGRect, @escaping (CGRect) -> CGRect, @escaping (CGRect) -> ()) -> ()) = { [weak self] oldFrame, layoutNewItems, animateIn in
UIView.animate(
withDuration: (animationDuration / 2),
delay: 0,
options: .curveEaseIn,
animations: {
self?.stackView.frame = oldFrame.offsetBy(
dx: 0,
dy: oldFrame.height
)
},
completion: { _ in
let updatedOldFrame: CGRect = layoutNewItems(oldFrame)
animateIn(updatedOldFrame)
}
)
}
let layoutNewItems: (CGRect) -> CGRect = { [weak self] oldFrame -> CGRect in
var updatedOldFrame: CGRect = oldFrame
// Update the UI (need to re-offset it as the position gets reset during
// during these changes)
UIView.performWithoutAnimation {
self?.stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
newCellViews.forEach { cellView in
self?.stackView.addArrangedSubview(cellView)
}
self?.cellViews = newCellViews
// Update the UI (need to re-offset it as the position gets reset during
// during these changes)
UIView.performWithoutAnimation {
self?.updateFocusedItem(focusedItem)
self?.isHidden = false
oldFrame = (self?.stackView.frame)
.defaulting(to: oldFrame)
self?.stackView.frame = oldFrame.offsetBy(
dx: 0,
dy: oldFrame.height
)
}
self?.updateFocusedItem(focusedItem)
self?.stackView.layoutIfNeeded()
self?.stackClippingView.isHidden = false
UIView.animate(
withDuration: (animationDuration / 2),
delay: 0,
options: .curveEaseOut,
animations: { [weak self] in
self?.stackView.frame = oldFrame
},
completion: nil
updatedOldFrame = (self?.stackView.frame)
.defaulting(to: oldFrame)
self?.stackView.frame = oldFrame.offsetBy(
dx: 0,
dy: oldFrame.height
)
}
)
return updatedOldFrame
}
let animateIn: (CGRect) -> () = { [weak self] oldFrame in
UIView.animate(
withDuration: (animationDuration / 2),
delay: 0,
options: .curveEaseOut,
animations: { [weak self] in
self?.stackView.frame = oldFrame
},
completion: nil
)
}
// If we don't have arranged subviews already we can skip the 'animateOut'
guard !self.stackView.arrangedSubviews.isEmpty else {
let updatedOldFrame: CGRect = layoutNewItems(self.stackView.frame)
animateIn(updatedOldFrame)
return
}
animateOut(self.stackView.frame, layoutNewItems, animateIn)
}
// MARK: - GalleryRailCellViewDelegate

View File

@ -1,15 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSArray (OWS)
- (NSArray<NSString *> *)uniqueIds;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,25 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "NSArray+OWS.h"
#import "TSYapDatabaseObject.h"
NS_ASSUME_NONNULL_BEGIN
@implementation NSArray (OWS)
- (NSArray<NSString *> *)uniqueIds
{
NSMutableArray<NSString *> *result = [NSMutableArray new];
for (id object in self) {
OWSAssertDebug([object isKindOfClass:[TSYapDatabaseObject class]]);
TSYapDatabaseObject *dbObject = object;
[result addObject:dbObject.uniqueId];
}
return result;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,7 +0,0 @@
#import <Foundation/Foundation.h>
@interface NSObject (Casting)
- (id)as:(Class)cls;
@end

View File

@ -1,10 +0,0 @@
#import "NSObject+Casting.h"
@implementation NSObject (Casting)
- (id)as:(Class)cls {
if ([self isKindOfClass:cls]) { return self; }
return nil;
}
@end

View File

@ -1,9 +0,0 @@
#import <Foundation/Foundation.h>
@interface NSSet (Functional)
- (BOOL)contains:(BOOL (^)(id))predicate;
- (NSSet *)filtered:(BOOL (^)(id))isIncluded;
- (NSSet *)map:(id (^)(id))transform;
@end

View File

@ -1,32 +0,0 @@
#import "NSSet+Functional.h"
@implementation NSSet (Functional)
- (BOOL)contains:(BOOL (^)(id))predicate {
for (id object in self) {
BOOL isPredicateSatisfied = predicate(object);
if (isPredicateSatisfied) { return YES; }
}
return NO;
}
- (NSSet *)filtered:(BOOL (^)(id))isIncluded {
NSMutableSet *result = [NSMutableSet new];
for (id object in self) {
if (isIncluded(object)) {
[result addObject:object];
}
}
return result;
}
- (NSSet *)map:(id (^)(id))transform {
NSMutableSet *result = [NSMutableSet new];
for (id object in self) {
id transformedObject = transform(object);
[result addObject:transformedObject];
}
return result;
}
@end

View File

@ -1,22 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
NSString *NSStringForUIGestureRecognizerState(UIGestureRecognizerState state);
// This custom GR can be used to detect touches when they
// begin in a view. In order to honor touch dispatch, this
// GR will ignore touches that:
//
// * Are not single touches.
// * Are not in the view for this GR.
// * Are inside a visible, interaction-enabled subview.
@interface OWSAnyTouchGestureRecognizer : UIGestureRecognizer
@end
NS_ASSUME_NONNULL_END

View File

@ -1,132 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSAnyTouchGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
NS_ASSUME_NONNULL_BEGIN
NSString *NSStringForUIGestureRecognizerState(UIGestureRecognizerState state)
{
switch (state) {
case UIGestureRecognizerStatePossible:
return @"UIGestureRecognizerStatePossible";
case UIGestureRecognizerStateBegan:
return @"UIGestureRecognizerStateBegan";
case UIGestureRecognizerStateChanged:
return @"UIGestureRecognizerStateChanged";
case UIGestureRecognizerStateEnded:
return @"UIGestureRecognizerStateEnded";
case UIGestureRecognizerStateCancelled:
return @"UIGestureRecognizerStateCancelled";
case UIGestureRecognizerStateFailed:
return @"UIGestureRecognizerStateFailed";
}
}
@implementation OWSAnyTouchGestureRecognizer
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
{
return NO;
}
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
{
return NO;
}
- (BOOL)shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return NO;
}
- (BOOL)shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
if (self.state == UIGestureRecognizerStatePossible && [self isValidTouch:touches event:event]) {
self.state = UIGestureRecognizerStateRecognized;
} else {
self.state = UIGestureRecognizerStateFailed;
}
}
- (UIView *)rootViewInViewHierarchy:(UIView *)view
{
OWSAssertDebug(view);
UIResponder *responder = view;
UIView *lastView = nil;
while (responder) {
if ([responder isKindOfClass:[UIView class]]) {
lastView = (UIView *)responder;
}
responder = [responder nextResponder];
}
return lastView;
}
- (BOOL)isValidTouch:(NSSet<UITouch *> *)touches event:(UIEvent *)event
{
if (event.allTouches.count > 1) {
return NO;
}
if (touches.count != 1) {
return NO;
}
UITouch *touch = touches.anyObject;
CGPoint location = [touch locationInView:self.view];
if (!CGRectContainsPoint(self.view.bounds, location)) {
return NO;
}
if ([self subviewControlOfView:self.view containsTouch:touch]) {
return NO;
}
// Ignore touches that start near the top or bottom edge of the screen;
// they may be a system edge swipe gesture.
UIView *rootView = [self rootViewInViewHierarchy:self.view];
CGPoint rootLocation = [touch locationInView:rootView];
CGFloat distanceToTopEdge = MAX(0, rootLocation.y);
CGFloat distanceToBottomEdge = MAX(0, rootView.bounds.size.height - rootLocation.y);
CGFloat distanceToNearestEdge = MIN(distanceToTopEdge, distanceToBottomEdge);
CGFloat kSystemEdgeSwipeTolerance = 50.f;
if (distanceToNearestEdge < kSystemEdgeSwipeTolerance) {
return NO;
}
return YES;
}
- (BOOL)subviewControlOfView:(UIView *)superview containsTouch:(UITouch *)touch
{
for (UIView *subview in superview.subviews) {
if (subview.hidden || !subview.userInteractionEnabled) {
continue;
}
CGPoint location = [touch locationInView:subview];
if (!CGRectContainsPoint(subview.bounds, location)) {
continue;
}
if ([subview isKindOfClass:[UIControl class]]) {
return YES;
}
if ([self subviewControlOfView:subview containsTouch:touch]) {
return YES;
}
}
return NO;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -3,10 +3,25 @@
//
import Foundation
import UIKit
extension UIGestureRecognizer {
@objc
public var stateString: String {
return NSStringForUIGestureRecognizerState(state)
return state.asString
}
}
extension UIGestureRecognizer.State {
fileprivate var asString: String {
switch self {
case .possible: return "UIGestureRecognizerStatePossible"
case .began: return "UIGestureRecognizerStateBegan"
case .changed: return "UIGestureRecognizerStateChanged"
case .ended: return "UIGestureRecognizerStateEnded"
case .cancelled: return "UIGestureRecognizerStateCancelled"
case .failed: return "UIGestureRecognizerStateFailed"
@unknown default: return "UIGestureRecognizerStateUnknown"
}
}
}