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:
parent
26c7a5022a
commit
8ff542405c
|
@ -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 */,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ?
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
//
|
||||
|
||||
#import "OWSWindowManager.h"
|
||||
#import "Environment.h"
|
||||
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
|
||||
#import <SessionUtilitiesKit/SessionUtilitiesKit.h>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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) }
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,7 +0,0 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSObject (Casting)
|
||||
|
||||
- (id)as:(Class)cls;
|
||||
|
||||
@end
|
|
@ -1,10 +0,0 @@
|
|||
#import "NSObject+Casting.h"
|
||||
|
||||
@implementation NSObject (Casting)
|
||||
|
||||
- (id)as:(Class)cls {
|
||||
if ([self isKindOfClass:cls]) { return self; }
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue